* feat(issues): add sub-issue support
- Backend: Add ListChildIssues SQL query, add parent_issue_id to UpdateIssue,
add GET /api/issues/{id}/children endpoint
- Frontend: Display parent issue breadcrumb and link in issue detail sidebar,
show child issues list with status icons, add "Create sub-issue" action in
dropdown menu and sidebar, pass parent_issue_id through create issue modal
- Update test mocks for new API method
* fix(issues): add parent validation, cycle detection, and improve child refresh
- CreateIssue: validate parent issue exists in the same workspace
- UpdateIssue: validate parent exists, prevent self-referencing, detect
circular parent chains (up to 10 levels deep)
- Frontend: derive child issues from store when available instead of
refetching on every global issue count change
* fix(daemon): update existing worktree to latest remote on reuse
When an agent receives a new task on the same issue, the execution
environment is reused and the repo worktree already exists on disk.
Previously, `multica repo checkout` would fail because `git worktree add`
cannot create a path that already exists — so the agent worked on stale
code from the prior task.
Now `CreateWorktree` detects existing worktrees and updates them:
fetch origin, reset working tree, then checkout a new branch from the
latest remote default branch. The previous task's branch is preserved.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(daemon): propagate actual branch name and use correct ref in worktree reuse
- Return (string, error) from updateExistingWorktree so collision-retried
branch name propagates to WorktreeResult
- Use baseRef directly instead of origin/baseRef — bare clone refspec maps
remote branches to local refs, so remote-tracking refs may not exist
- Remove redundant fetch (worktree shares object store with bare clone)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Separate ReportTaskUsage endpoint (POST /api/daemon/tasks/{id}/usage)
so usage is captured independently of complete/fail — fixes usage loss
for failed/blocked tasks.
2. Add usage tracking for all four providers:
- Claude: already done (stream-json message.usage)
- OpenCode: extract from step_finish.part.tokens
- OpenClaw: extract from step_end.data token fields
- Codex: extract from turn/completed and task_complete usage fields
3. Remove usage from CompleteTask payload — all usage goes through the
dedicated endpoint now.
Extract token usage from Claude Code's stream-json output in real-time
during task execution, replacing the inaccurate global JSONL log scanner.
- New `task_usage` table: tracks (task_id, provider, model) level usage
- Agent SDK: parse `message.usage` from assistant messages, accumulate
per-model and return in Result
- Daemon: convert agent usage to entries, send with CompleteTask
- Server: store usage on task completion, expose workspace-level
aggregation APIs (GET /api/usage/daily, GET /api/usage/summary)
* refactor: remove agent triggers config field
Remove the triggers field from agent configuration. The on_assign,
on_comment, and on_mention behaviors are now always enabled (hardcoded),
as decided in the Agentflow design discussion (MUL-372).
Changes:
- Database: migration 032 drops triggers column from agent table
- Backend: remove triggers from create/update agent APIs and response
- Backend: simplify trigger-checking logic to always-enabled
- Frontend: remove TriggersTab UI and AgentTrigger types
- Tests: remove trigger config unit tests (no longer configurable)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: also remove agent tools config field
Remove the tools field from agent configuration alongside triggers.
The tools field was a placeholder — stored in the DB and shown in the
UI but never passed to the daemon or used at runtime.
- Database: migration 032 now also drops tools column
- Backend: remove tools from create/update agent APIs and response
- Frontend: remove ToolsTab UI, AgentTool type, and tools tab
- Update landing page copy
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(test): remove tools/triggers columns from test fixtures
The test fixtures still referenced the dropped tools and triggers
columns when inserting agent rows, causing CI failures.
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>
- Relax ClaimAgentTask SQL constraint from per-issue to per-(issue, agent)
serialization, allowing different agents to run in parallel on the same issue
- Update GetActiveTaskForIssue API to return all active tasks (array) instead of
just the first one
- Refactor AgentLiveCard to render one card per active task, routing WebSocket
messages by task_id for independent timelines
- Fix shouldEnqueueOnComment to use per-agent dedup so a mentioned agent's
pending task doesn't block the assigned agent's on_comment trigger
Closes MUL-160
- Add ListOpenIssues SQL query (excludes done/cancelled, no LIMIT)
- Add CountIssues SQL query for true total count
- Backend: support open_only=true param, fix total to return real count
- Frontend: two-phase fetch in issue store (all open + first 50 closed)
- Add fetchMoreClosed action for paginated closed issue loading
- Replace all hardcoded limit:200 with store.fetch() calls
Resolves MUL-369
Co-authored-by: Devv <devv@Devvs-Mac-mini.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Support Google login that links to existing accounts by email.
When a user who registered via email OTP signs in with Google using
the same email, they are linked to the same account.
Backend:
- Add POST /auth/google endpoint that exchanges Google auth code for
tokens, fetches user profile, and calls findOrCreateUser()
- Updates user name and avatar from Google profile on first Google login
Frontend:
- Add "Continue with Google" button on login page (shown when
NEXT_PUBLIC_GOOGLE_CLIENT_ID is configured)
- Add /auth/callback page to handle Google OAuth redirect
- Add loginWithGoogle to auth store and API client
When repos are present, sub-steps c/d/e/f are now distinct instead of
having two 'c' steps. Each branch (with/without repos) now has its own
complete set of correctly lettered sub-steps.
Prepend the directory of the running multica binary to PATH in the
agent's environment variables. This fixes the issue where isolated
runtimes (e.g. Codex sandbox) cannot find the multica CLI, causing
agent tasks to fail immediately with "command not found: multica".
Closes#451
Add OpenClaw as a fourth supported agent runtime alongside Claude Code,
Codex, and OpenCode. OpenClaw CLI (`openclaw agent -p ... --output-format
stream-json`) is integrated via the same Backend interface pattern.
Changes:
- Add openclawBackend in server/pkg/agent/openclaw.go with NDJSON
event stream parsing (text, thinking, tool_call, error, step, result)
- Register "openclaw" in the agent factory (agent.go)
- Add MULTICA_OPENCLAW_PATH / MULTICA_OPENCLAW_MODEL env var detection
in daemon config
- Include "openclaw" in AGENTS.md config injection alongside codex/opencode
- Add comprehensive unit tests for all event handlers and processEvents
The assignee check in enqueueMentionedAgentTasks silently skipped
explicit @mentions when the target agent was the issue assignee in
a non-terminal status. This broke the review-rejection-retry loop:
when a reviewer rejected a PR and @mentioned the developer agent,
the mention was skipped because the developer was the assignee.
The downstream HasPendingTaskForIssueAndAgent check already prevents
duplicate queued tasks, making the assignee skip redundant. Removing
it ensures explicit @mentions always fire regardless of assignee status.
Closes#431
- Use google/uuid NewV7() for attachment ID and S3 file key instead of
random hex, so the S3 object name matches the attachment record ID
- Add LinkAttachmentsToIssue query to associate orphaned attachments
with a newly created issue
- Pass attachment_ids in CreateIssue request so uploads during issue
creation (before the issue exists) get linked after commit
- Collect and pass attachment IDs in comment-input and reply-input
so comment creation properly links uploaded files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(cli): overhaul help output to match gh CLI style
- Add gh-style grouped help with CORE/RUNTIME/ADDITIONAL COMMANDS sections
- Use UPPERCASE section headers (USAGE, FLAGS, EXAMPLES, LEARN MORE)
- Format commands as "name: description" with automatic alignment
- Add ENVIRONMENT VARIABLES and EXAMPLES sections to root help
- Apply consistent templates to root, subcommand, and leaf commands
- Update descriptions from "Manage X" to "Work with X" for gh parity
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(execenv): add explicit instruction for agents to always use multica CLI
Agents were using curl/wget to access Multica attachment URLs directly,
which fails due to authentication. Add a prominent "Important" section
to the generated CLAUDE.md template that explicitly prohibits direct
HTTP access and instructs agents to escalate missing CLI functionality
to their workspace owner.
---------
Co-authored-by: Devv <devv@Devvs-Mac-mini.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a reply in a thread explicitly mentions only non-agent entities
(members or issues), do not inherit agent mentions from the parent
comment. This prevents false agent triggers when a user is directing
their reply at other people (e.g. "cc @Someone") rather than requesting
work from agents mentioned in the thread root.
Fixes MUL-324
The assignee was unconditionally skipped in the mention path, assuming
on_comment would handle it. But on_comment is suppressed for terminal
statuses (done/cancelled), so an explicit @mention of the assignee had
no effect. Now only skip the assignee dedup when on_comment will
actually fire (non-terminal status).
1. Update CLAUDE.md template to document --limit, --offset, --since
params and guide agents to use pagination when comments are large
2. Add GetJSONWithHeaders to API client; CLI now prints "Showing X of Y
comments" to stderr when paginating
3. Cap --since without --limit at 500 server-side to prevent unbounded
result sets
The inbox UI deduplicates items by issue_id (showing only the latest
notification per issue). Previously, clicking archive only archived the
single visible item, so older items for the same issue would reappear.
Now archiving operates at the issue level — both the backend and frontend
archive all inbox items sharing the same issue_id.
Add --limit, --offset, and --since flags to `multica issue comment list`
to prevent context window overflow when issues have many comments.
The API endpoint now accepts limit, offset, and since (RFC3339) query
parameters. When paginating, the response includes an X-Total-Count
header with the total number of comments.
When a member-started thread root @mentions the assignee agent, replies
in that thread should trigger on_comment — the thread is a conversation
with the agent, not a member-to-member chat.
Previously isReplyToMemberThread only checked the reply content for
assignee mentions. Now it also checks the parent (thread root) content.
This fixes a gap where path 1 (on_comment) suppressed the trigger and
path 2 (on_mention) skipped the assignee, leaving no trigger path.
When a top-level comment @mentions an agent (non-assignee), subsequent
replies in the same thread now also trigger that agent via on_mention.
Previously only the current comment's mentions were checked, so replies
without an explicit re-mention would silently skip the agent.
Extends enqueueMentionedAgentTasks to accept the parent comment and
merge its parsed mentions (deduplicated) into the trigger set, reusing
all existing guards (self-trigger, assignee skip, visibility, dedup).
Closes MUL-177
* fix(agent): instruct agents to use download_url for attachments
Agents were not aware of the signed vs unsigned URL distinction in
attachments, causing failures when trying to read images. Added an
Attachments section to the generated CLAUDE.md/AGENTS.md template that
tells agents to always use `download_url`. Also increased signed URL
expiry from 5 to 30 minutes to better accommodate agent processing time.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(cli): add `multica attachment download` command
Adds a dedicated CLI command for downloading attachments by ID. The
command fetches attachment metadata from the API (which returns a fresh
signed URL), downloads the file, and saves it locally. This eliminates
the need for agents to understand signed vs unsigned URLs.
Changes:
- New `multica attachment download <id>` CLI command
- New `GET /api/attachments/{id}` backend endpoint
- `DownloadFile` helper on APIClient
- Updated CLAUDE.md template to document the command
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): sanitize filename and add download size limit
- Use filepath.Base on attachment filename to prevent path traversal
- Add 100MB size limit to DownloadFile (matches upload limit)
- Include response body in download error messages for debugging
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>
* feat(daemon): add opencode as supported agent provider
Add opencode backend alongside claude and codex. The backend spawns
`opencode run --format json`, parses streaming JSON events (text,
tool_use, error, step_start/finish), and supports --prompt for system
prompts. Includes CLI detection, AGENTS.md runtime config, native skill
discovery via .config/opencode/skills/, and 21 tests covering handlers,
JSON parsing, and integration-level processEvents scenarios.
* chore: add .tool-versions to gitignore
* feat(agent): replace hard delete with archive/restore
Replace agent deletion with soft archive pattern. Archived agents
are preserved in the database with all historical references intact
but cannot be assigned, mentioned, or trigger tasks.
Backend:
- Add archived_at/archived_by columns to agent table (migration 031)
- Replace DELETE /api/agents/{id} with POST /api/agents/{id}/archive
- Add POST /api/agents/{id}/restore endpoint
- ListAgents excludes archived by default (?include_archived=true to include)
- Skip archived agents in task triggers (on_assign, on_comment, on_mention)
- Block assignment to archived agents
- Cancel pending tasks on archive
- New events: agent:archived, agent:restored (replacing agent:deleted)
Frontend:
- Agent type includes archived_at/archived_by fields
- Mention autocomplete and assignee picker filter out archived agents
- Agent list shows archived agents with muted styling
- Agent detail shows archive banner with restore button
- Delete button replaced with Archive button and updated confirmation dialog
- API client: archiveAgent/restoreAgent replace deleteAgent
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(agent): self-review fixes for archive feature
- Fix: workspace store now fetches agents with include_archived=true
so archived agents are actually visible in the frontend (the archived
UI was dead code before — ListAgents excludes archived by default)
- Fix: add error logging for CancelAgentTasksByAgent in ArchiveAgent
- Fix: add idempotency guards — return 409 Conflict when archiving
an already-archived agent or restoring a non-archived agent
- Fix: revert unnecessary extra GetAgent query in ReconcileAgentStatus
(archived agents won't have running tasks after CancelAgentTasksByAgent)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(daemon): support direct download update for non-Homebrew installs
Previously, CLI auto-update only worked for Homebrew installations. Non-brew
binaries would fail with "not installed via Homebrew". Now the daemon and
`multica update` fall back to downloading the release binary directly from
GitHub Releases when Homebrew is not detected.
Also fixes:
- Daemon restart now uses the current executable's absolute path instead of
searching PATH, ensuring the updated binary is used
- Brew installs preserve the symlink path so the new Cellar version is picked up
- Daemon startup logs now include the CLI version
- Update UI auto-clears "restarting" status after 5s to show the new version
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): remove dead DetectNewBinaryPath and guard against nil latest version
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The runtime detail page was showing the agent CLI version (claude/codex)
as "CLI Version" because metadata.version stored the agent version from
agent.DetectVersion(). The multica CLI version was never sent.
Fix: daemon now sends cli_version in the registration request, server
stores it as metadata.cli_version alongside the existing agent version,
and frontend reads metadata.cli_version.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(runtime): support CLI update from web runtime page
Add the ability to update the CLI daemon from the web Runtime detail page.
When a newer version is available on GitHub Releases, an update button
appears. Clicking it sends an update command through the server to the
daemon via the heartbeat mechanism (same pattern as ping). The daemon
executes `brew upgrade`, reports the result, and restarts itself with the
new binary.
Changes across all three layers:
- Frontend: version display, GitHub latest check, UpdateSection component
- Server: UpdateStore (in-memory), heartbeat extension, 3 new endpoints
- CLI: shared update logic, daemon handleUpdate + graceful restart
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(runtime): handle 'running' status in ReportUpdateResult
The daemon sends {"status":"running"} when it starts executing the
update, but ReportUpdateResult treated any non-"completed" status as
failure — immediately marking the update as failed before brew upgrade
even ran.
Fix: use a switch statement to handle "running" as a no-op (status is
already "running" from PopPending), and also timeout running updates
after 120 seconds in case brew upgrade hangs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(upload): remove file type allowlist to support all file types
Removes the hardcoded MIME type allowlist from both frontend and backend
that was blocking uploads of file types like Word documents (.docx).
File size limit (10 MB) is still enforced. Content type detection is
preserved for metadata storage.
Closes MUL-123
* feat(upload): increase file size limit from 10 MB to 100 MB
Updates both frontend and backend to allow uploads up to 100 MB.
* feat(mentions): support @mentioning issues in comments
- Extend MentionItem type to include "issue" alongside "member"/"agent"
- Add issue search (by identifier and title) to mention suggestion dropdown
- Render issue mentions with CircleDot icon in autocomplete popup
- Issue mentions serialize as [MUL-117 Title](mention://issue/id) (no @ prefix)
- Markdown renderer shows issue mentions as clickable links to /issues/:id
- Backend mentionRe regex updated to match issue mention type
* feat(mentions): auto-expand issue identifiers and add mention format to agent instructions
1. Path A — CLAUDE.md template (runtime_config.go):
Add a "## Mentions" section teaching agents the mention serialization
format for issues, members, and agents. All agents automatically
receive this via the auto-generated CLAUDE.md.
2. Approach 2 — Server-side auto-conversion (internal/mention/):
New ExpandIssueIdentifiers() utility that scans comment content for
bare issue identifiers (e.g. MUL-117) and replaces them with
[MUL-117](mention://issue/<uuid>) mention links. Skips code blocks,
inline code, and existing markdown links. Integrated into both:
- handler.CreateComment (HTTP API path)
- service.createAgentComment (agent task output path)
When clicking an inbox notification, the issue detail now scrolls to and
briefly highlights the relevant comment. Also adds a floating "Jump to
bottom" button on issue pages with long timelines.
Backend: store comment_id in inbox notification details for new_comment
and reaction_added events. Frontend: pass highlightCommentId through to
IssueDetail, add id attributes to comment elements, and track scroll
position for the jump-to-bottom button.