1113 Commits
Author SHA1 Message Date
Claude 53da050caf Merge branch 'claude/project-thread-nhut62' (PR #6) into zai-beta
Before: zai-beta carried the platform admin console and open-connector
work (#7, #8, #9) on top of main.

After: it also carries the project feed view, the deliverable focus view,
the project sidebar panels and the issue-detail performance work from
PR #6, so the zai-beta images include every integration branch feature.

How: merge with two append-only conflict resolutions
(concurrentIndexCleanups in server/cmd/migrate/main.go and the schema
exports in packages/core/api/schemas.ts); migrations 585-587 do not
collide with 590-606. CHANGELOG-zai.md gains the PR #6 section.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_017deksZDSzZBA4QSqZZoBip
2026-10-21 10:16:52 +00:00
Claude 2163b902e4 feat(projects): project attachments endpoint
Before: a project had no way to list the files its issues carry, and no
issue to attach a file uploaded straight to the project.

After: GET /api/projects/{id}/attachments lists the attachments of every
issue in the project, newest first, paged, filterable by source
(deliverable = agent upload, upload = member upload), with each row's
issue {id, identifier, title}, the total, and both group totals. POST
/api/projects/{id}/library-issue returns the project's "Project files"
issue {id, identifier}, creating it on first use as a top-level,
unassigned backlog issue.

How: one sqlc join attachment -> issue on attachment.issue_id (comment
attachments keep their issue_id, so each row appears once), excluding
tombstoned comments and source-context snapshot copies; existing
idx_issue_project_status and idx_attachment_issue cover the join, so no
index is added. The category filter stays client-side because the
frontend buckets files with preview-kind and extension tables that
cannot be mirrored here without a second copy. The holder issue is
created through IssueService.Create, whose per-(project, title)
duplicate lock makes concurrent first uploads converge on one issue.
DB-backed tests are written and compile-checked but not run here.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_017deksZDSzZBA4QSqZZoBip
2026-10-21 07:51:55 +00:00
Claude 9df876cae2 feat(projects): project pull requests endpoint
Before: a project had no way to list the pull requests its issues delivered; the only PR list was per issue, so a project-level view would have to fan out one request per issue and could not page or count them.

After: GET /api/projects/{id}/pull-requests returns the distinct GitHub and self-hosted VCS PRs linked to the project's issues, newest update first, with the issue PR list's fields (checks rollup, mergeable, additions, deletions) plus the project issues each PR is linked to. state=open|merged|closed filters (open covers draft, closed is closed without merging), limit/offset page per PR, and the response carries total and per-group counts.

How: ListProjectPullRequestPage unions both PR tables and de-duplicates links so paging is per PR; ListProjectPullRequests / ListProjectVCSPullRequests then load full rows for the page ids with the same columns as the per-issue queries, so the existing row serializers are reused unchanged (including the page-visit snapshot refresh). Issues are attached from one extra query. CountProjectPullRequestsByState feeds total and counts. No migration: additions/deletions already exist. DB-backed handler tests are written but not run here (no PostgreSQL).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_017deksZDSzZBA4QSqZZoBip
2026-10-21 07:44:55 +00:00
Claude 5ca20d1123 feat(projects): return the newest reply excerpt in thread summaries
Before: GET /api/issues/thread-summaries told a client how many replies an issue had and when the last one landed, but not what it said, so a compact list had to open the thread to show it.

After: each summary row carries last_reply_excerpt, the newest counted (non-deleted, non-system) comment flattened to one line of at most 80 runes with mentions collapsed to @Label; null when the issue has no counted comment.

How: ListIssueThreadSummaries joins a lateral lookup of the newest counted comment (body capped at 2000 characters in SQL) onto the existing rollup, so the batch is still one query; the handler flattens it with commentExcerpt. A DB-backed handler test covers newest-wins, deleted and system exclusion, mention flattening and truncation (not run here, no PostgreSQL).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_017deksZDSzZBA4QSqZZoBip
2026-10-21 07:44:08 +00:00
Claude dc60823c7c feat(issues): show PRs and deliverables on the feed's output strip
Before: a feed card's thread bar only said how many replies an issue had.
To see whether an agent had opened a PR, whether CI was green, or whether a
report had been uploaded, the reader had to open the issue.

After: GET /api/issues/thread-summaries also returns, per issue, up to 3
linked PRs (newest first, with state, diff size and a passed/failed/pending/
none CI verdict), the number of distinct deliverable files and when the
newest was uploaded. The thread bar ends with one chip per PR (state icon and
CI icon, opens the PR in a new tab) and one deliverables chip (count and age,
opens the issue). The peek shows the same strip under its header when the
issue has any output. Issues with a PR or file but no comment now get a row.

How: three batched, workspace-scoped queries over all requested ids (GitHub
PRs, self-hosted VCS PRs, deliverables); PRs are ranked per issue in SQL and
merged in Go. checks_state reuses rollupToConclusion and
aggregateChecksConclusion, so it agrees with the PR list. Deliverables are
files on live comments, grouped by media type and filename like
collectDeliverableFiles. The response schema defaults the new fields for older
servers and maps unknown checks_state values to none. The summary cache is
refetched on pull_request events and on comments that carry files. DB-backed
handler tests were written but not run (no Postgres here).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_017deksZDSzZBA4QSqZZoBip
2026-10-21 06:27:43 +00:00
Claude 13af0e9e3e feat(issues): show the agent's latest progress checklist in the feed
Before: The feed gave no sense of how far an agent had got. Agents could
not post a progress report that the feed understands: `comment add` had no
way to set a comment type, and the run brief told agents not to post
progress at all.

After: Each feed message shows a compact checklist under its thread bar
(done/total, a small bar, the first 5 items, "+N more"), and the issue peek
shows the same block under its title. Finished issues (done or closed
category) fold to a single "done/total" line with a chevron to expand. A
progress report is a `progress_update` comment by an agent whose body is a
Markdown task list; the newest one per issue wins.

How: The thread-summaries endpoint gains `latest_progress`
({comment_id, content, created_at} | null) from one batched DISTINCT ON
query, so there is no new endpoint. Core adds the type, a tolerant zod
schema, `parseProgressChecklist`, and a realtime patch for new reports.
Views add `FeedProgressCard`, mounted from the thread bar and the peek, with
strings in all five locales. `multica issue comment add --type
progress_update` lets agents post a report, and the run brief documents it as
the one exception to the single-comment rule.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_017deksZDSzZBA4QSqZZoBip
2026-10-21 06:10:12 +00:00
Claude 5010d451a0 test(server): classify connector_service_policy in the workspace deletion manifest
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01G4Tynn8YQnR5koJTSvFC62
2026-10-21 06:05:59 +00:00
Claude c479d44c49 Merge remote-tracking branch 'origin/claude/project-thread-qefpx3' into claude/project-thread-s663w0 2026-10-21 05:59:05 +00:00
Claude 1cc41cb00b feat(server): open-connector service policy and admin operations API
Add connector_service_policy (per-service publish switch, tool allowlist,
user allowlist) and gate the open-connector catalog, connect flows and task
overlays on it. Publish open_connector_apps from whether the integration is
wired instead of a feature flag, and drop OPEN_CONNECTOR_API_KEY_SERVICES.
Expose /api/admin/open-connector/* (health probe, service catalog with
policy and connection counts, connections by user, audited enable/disable
and policy writes through runAdminAction).

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01G4Tynn8YQnR5koJTSvFC62
2026-10-21 05:59:04 +00:00
Claude 2ce16d8842 Merge origin/main into claude/project-thread-qefpx3
Resolves the UpdateAgent conflict in handler/agent.go by keeping both the
connector_service_allowlist write and main's runtime-binding transaction,
renumbers the open-connector migrations to 590-596 because main took 583/584,
and regenerates sqlc.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XqqpKySn2iWtXriK3Gsgak
2026-10-21 05:56:59 +00:00
Claude 5802a4469a feat(issues): surface agent questions waiting on the reader in the feed
Before: a feed card showed reply counts and an unread dot, but an agent
asking the reader to decide something looked like any other reply.

After: GET /api/issues/thread-summaries returns awaiting_me per issue
(up to 3, newest first): unresolved threads whose latest comment is
agent-authored and mentions the caller. FeedView renders one decision
card between the message card and the thread bar with the agent avatar,
the newest excerpt, a "+N" count and a button that opens the peek on that
comment.

How: new sqlc query ListAgentQuestionsForIssues picks the latest
non-deleted, non-system comment of each unresolved thread (recursive
walk from the root, like ListCommentRepliesForRoots) and matches
mention://member/<caller> only on that row. The handler flattens mention
markup to @Label and cuts the excerpt to 140 runes. Peek actions gain an
optional { commentId } that reaches IssueDetail as highlightCommentId.
comment:created keeps the server-owned list, clears it for the caller's
own member comment and refetches when the comment may change it.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_017deksZDSzZBA4QSqZZoBip
2026-10-21 05:40:58 +00:00
Claude 7733c92ed5 Merge PR #8 (open-connector) into PR #7 (admin console) base
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01G4Tynn8YQnR5koJTSvFC62
2026-10-21 05:36:27 +00:00
Claude 3ad79c8548 feat(issues): per-user read cursors with unread dots on the feed
Before: the feed could not tell which conversations had moved since the
reader last looked, so every card read the same.

After: opening an issue (page or peek) records a per-user read cursor after
1.5 s, posting a comment records it at once, and feed cards whose newest
counted comment is later than the cursor carry a small dot on the left: a
plain dot when the reader spoke last, a ringed one when someone else did
and the reply is theirs to make.

How: table issue_read_cursor (no primary key in the create, a CONCURRENTLY
built unique index on (user_id, issue_id) attached as the primary key in a
later migration, no foreign keys), queries UpsertIssueReadCursor (monotonic
GREATEST on the database clock), ListIssueReadCursors and the three
cleanup deletes wired into issue deletion, member removal and workspace
teardown. PUT /api/issues/{id}/read returns 204; thread-summaries gains
last_read_at for the caller. Core adds api.markIssueRead, useMarkIssueRead
and patchThreadSummariesLastRead; the comment-created patch keeps the
cursor. Views add getFeedReadState and the dot on FeedMessageCard, the
delayed mark in IssueDetail and the immediate mark on send in
useIssueTimeline.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_017deksZDSzZBA4QSqZZoBip
2026-10-21 05:07:17 +00:00
Claude 751c983eb0 perf(issues): fetch child progress for the open issue's children only
Before: the issue detail subscribed to the workspace-wide child-progress map (every parent in the workspace) just to draw nested progress rings on its own sub-issue rows, a large response in big workspaces for a tiny need.

After: the detail asks for progress of exactly the loaded sub-issue ids, so the response is bounded by the visible rows. Board, list and swimlane keep the workspace-wide query unchanged, and the endpoint without parent_ids behaves as before.

How: GET /api/issues/child-progress accepts an optional parent_ids filter (comma-separated uuids, at most 100, malformed or oversized input is a 400) backed by the new ChildIssueProgressForParents query, which stays workspace-scoped. In core, childIssueProgressForParentsOptions sorts, dedupes and chunks ids at 100 and nests its key under issueKeys.childProgress so existing realtime and mutation invalidations still reach it.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_017deksZDSzZBA4QSqZZoBip
2026-10-21 03:55:36 +00:00
Claude 642d75483a Merge remote-tracking branch 'origin/main' into claude/project-thread-nhut62 2026-10-21 03:34:01 +00:00
Claude c4b6140334 perf(issues): page the comment timeline by root comment
Before: GET /issues/{id}/timeline read every comment and activity of the
issue (up to the hard cap) in one response, and the paging params only
changed the response wrapper. The issue page fetched, parsed and held the
whole timeline no matter how long the conversation was.

After: `roots` (1..200) pages by root comment, newest first, with a
(created_at, id) cursor in `next_before` / `next_before_id`. Each page carries
every reply of its roots, so a thread is never split, plus the activities of
the page's time window; `has_more_before` is exact. `around=<commentId>` lands
on the page that starts at the comment's thread root. Requests without
`roots` (including the legacy limit/before/after/around) behave exactly as
before, so installed clients still receive the whole timeline. The issue page
loads the newest 50 roots, fetches an older page when the list scrolls to its
top without moving the viewport, and walks older pages for deep links and
in-page find.

How: three new sqlc queries (roots before a cursor, replies of a set of roots
via a recursive walk, activities in a time window) feed the new
listTimelinePage path. The web cache becomes an infinite query whose newest
page is pages[0]; older pages are its next pages. Realtime and optimistic
patches go through updateTimelinePages in core, which applies a flat-list edit
across pages. Replies per page are capped at the comment hard cap: trailing
threads move to the next page, and only a single oversized thread is cut and
reported through X-Timeline-Truncated.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_017deksZDSzZBA4QSqZZoBip
2026-10-21 03:24:28 +00:00
Claude 3714b01172 perf(issues): load sub-issues on demand with counts on the detail response
Before: opening an issue detail always fetched every sub-issue (even with the
section collapsed) and re-fetched them on every mount, so an issue with many
sub-issues was slow to open.

After: GET /api/issues/{id} returns child_count and child_done_count, the
collapsed header and the unsubscribe control read those, and the list is only
fetched once the section is expanded, 50 at a time, with a "load more" row.
GET /api/issues/{id}/children accepts optional limit/offset (limit 1..200)
and always returns a total beside the issues array; without params it still
returns every child. A backend that predates the counts falls back to loading
the full list as before.

The refetch-on-every-mount guard is replaced by realtime invalidation: child
creation, status change, re-parent and deletion now also invalidate the
parent's detail, and child status patches reach loaded child pages.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_017deksZDSzZBA4QSqZZoBip
2026-10-21 02:22:56 +00:00
Claude b7523355d1 fix(issues): keep sub-second precision in thread summary last_reply_at
The thread summary test compares last_reply_at against the microsecond
timestamp it inserted, but the handler formatted it with second precision,
so the comparison failed on every run whose fixture time had a fractional
second. Format the field like other ordering fields, preserving what
PostgreSQL stored.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_017deksZDSzZBA4QSqZZoBip
2026-10-21 02:20:14 +00:00
Claude cdc9a5a417 feat(issues): batched thread summaries endpoint for the project feed
Before, a feed card needed a full comment list per issue to show reply
counts, open threads and who has been talking.

Now GET /api/issues/thread-summaries?ids=a,b,c (max 100) returns one rollup
per issue in the caller's workspace: counted comments, last comment time,
open (unresolved root) thread count and the three most recent participants.
Deleted and platform-authored (system) comments are not counted. Issues with
no counted comments, or from another workspace, simply have no row.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_017deksZDSzZBA4QSqZZoBip
2026-10-21 01:57:09 +00:00
klinsmayaandClaude Fable 5.1 1bc6a4db87 feat(server): open-connector HTTP routes, agent allowlist and token reaper
Expose the open-connector integration over /api/integrations/open-connector
(apps, OAuth and API-key connect, connection list/patch/delete, and the
public OAuth callback), wire it from OPEN_CONNECTOR_* environment
variables behind the open_connector_apps feature flag, add the
owner-only agent.connector_service_allowlist field, and register a
scheduler job that releases stale per-task runtime tokens and expired
OAuth states.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XqqpKySn2iWtXriK3Gsgak
2026-10-20 17:43:54 +00:00
klinsmayaandClaude Fable 5.1 8e04e8ba25 feat(server): open-connector connection service and task token lifecycle
Add the open-connector business integration: user-owned OAuth and
API-key connections mirrored in user_connector_connection, a DB-backed
OAuth state table so callbacks survive restarts and replicas, and a
per-task runtime token minted at enqueue with allowedConnections pinned
to the agent owner's connections in the agent's service allowlist. The
token id is stored in agent_task_queue.runtime_connector_handles and
released when the task reaches a terminal state; a reaper entry point
releases stale handles and expired OAuth states.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XqqpKySn2iWtXriK3Gsgak
2026-10-20 17:32:05 +00:00
Claude caf2cfa5dd feat(server): platform admin API with audit log and DB-backed disabled users
Adds the backend for the operations console: platform_admin roles
(super_admin / operator / viewer), an admin_audit_log table, and
user.disabled_at so disabling an account is a stored state instead of a
hard-coded denylist. The in-process disabled set is now refreshed from the
database every 30 seconds, and the old emergency denylist entries are
migrated into it.

/api/admin exposes overview counts, user and workspace listings with
detail, disable/enable, token revocation, the audit log, and platform
role management. Every write requires a reason and is recorded with the
actor, IP and user agent. PLATFORM_ADMIN_EMAILS bootstraps the first
super_admin.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01FrVjAFWeckYV69HS51xUhy
2026-10-20 17:07:24 +00:00
klinsmayaandClaude Fable 5.1 a21b56abe8 refactor(server): abstract connector providers behind runtimeapps.ConnectorProvider
Replace the Composio-specific ComposioOverlayBuilder seam on TaskService
with a ConnectorProviders slice so a second connector backend
(open-connector) can contribute a per-task MCP overlay without touching
the enqueue paths. Composio becomes the first implementation; overlays
from several providers merge by mcpServers key. Behavior is unchanged
for deployments with only Composio configured.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XqqpKySn2iWtXriK3Gsgak
2026-10-20 17:06:58 +00:00
m2keloopandm2keloop d908e4882d MUL-6710: fix(runtimes): revoke access when making runtime private (#7580)
* fix(runtimes): revoke access when making runtime private

* fix(runtimes): regenerate queries for workflow step

---------

Co-authored-by: m2keloop <[email protected]>
2026-10-20 12:49:37 +08:00
Dante a790bc8c08 fix(comments): retain delivered supplement root owners (#9061) 2026-10-19 22:22:32 -04:00
Riley Rogersandsoulmate 2f247cc92f fix: respect explicit comment-trigger cancellation during reconciliation (#9300)
Co-authored-by: soulmate <[email protected]>
2026-10-19 03:15:40 -04:00
Dante 5e32365b39 fix(agent): reject invalid private runtime bindings (#8981) 2026-10-19 15:10:28 +08:00
de198cd4c1 feat: allow quick actions without a target (MUL-7990) (#9210)
* test: cover quick actions without an agent

* feat: allow quick actions without a target

* test(quick-actions): cover clearing an existing agent target

* fix(quick-actions): keep existing locale copy

* test: consolidate quick-action regressions in existing test file

* chore(migrations): renumber quick action optional target migration to 584

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Multica Eve <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-19 14:41:48 +08:00
LinYushenandmultica-agent 6dc20dd314 feat(llm): use GPT-6 Luna as the built-in assist default (#9329)
Co-authored-by: multica-agent <[email protected]>
2026-10-19 14:23:43 +08:00
dfa8bdaa53 MUL-8070: feat(cursor): support live-run steering via Cursor hooks (#9322)
* MUL-8070: feat(cursor): support live-run steering via Cursor hooks

Cursor's headless --print mode has no mid-turn input channel, but its
postToolUse/postToolUseFailure hooks can return additional_context that
reaches the model in the same turn. A steerable Cursor run now writes a
managed .cursor/hooks.json whose command is the multica binary in a hidden
relay mode; the relay forwards the hook event to a token-protected loopback
endpoint owned by the run, which hands back pending guidance tagged with a
per-run nonce. The prompt declares that tag so the model treats the context
as genuine user guidance instead of a prompt injection.

Steering is offered only for daemon-managed workdirs on Unix with cursor-agent
2026.10.14+, and the server allowlists cursor for capability negotiation,
supplement creation and composer binding.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* MUL-8070: fix(cursor): confirm steering only on Cursor's receipt and gate on installable hooks

- Count hook-delivered guidance as delivered only when the tool call's
  completed stream event echoes it in hookAdditionalContexts. Guidance
  Cursor dropped for the same call is retried at the next tool boundary;
  guidance without a receipt fails when the turn ends.
- Offer steering only when the workdir can carry the managed hooks.json,
  and fail supplements immediately if installation still fails after
  negotiation.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-18 16:23:05 +08:00
700d25eef5 MUL-7420: render the workflow brief once on the server, for the assignee or an observer (#9293)
- issueworkflow.Brief is the only renderer: the claim carries it into every
  run's per-turn prompt; the editor preview and timeline show the same text.
- Workflow handoffs stop writing the brief into handoff_note, so a person's
  note no longer drops the brief and the handoff framing fits again.
- Agents that are not the issue's assignee get an observer brief without
  the step's commands.
- Clearer rule override and narrower wakeup guidance; user text is
  stripped of control characters.
- multica-platform skill documents project workflows.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-16 17:28:45 +08:00
ec1e931742 MUL-8049: fix(workflows): validate the handler in the brief preview (#9294)
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-16 17:25:29 +08:00
da1671ca20 MUL-7420: Project workflows — columns and handoffs on the workflow (#8814)
* feat(workflows): workspace workflows with step handoffs (MUL-7420)

A workflow is a named, workspace-level selection of statuses from the
shared status catalog, in board order, with an optional handoff on each
step. Projects opt in through project.workflow_id; a project without one
keeps the whole catalog (the implicit Default workflow), so existing
workspaces see no change.

- issue_workflow table + project.workflow_id, CRUD API under
  /api/issue-workflows, POST /api/projects/{id}/workflow with a dry-run
  status mapping plan. Only issues on statuses the target workflow does
  not list are remapped, and remapping never hands issues off.
- Issue writes keep the status inside the project's workflow. Entering a
  step with a handler through a status change assigns the issue to that
  handler and starts its run with the step's brief as the handoff note;
  an explicit assignee in the same write wins. Creating an issue starts
  at the workflow's starting step.
- Task claims carry the issue's project workflow, and the brief renders
  a Project Workflow section so agents move along this project's
  statuses instead of the built-in ones.
- Statuses a workflow lists cannot be archived.
- Adoption is gated by the project_workflows_v1 flag; existing
  workflows keep working and projects can always switch back to Default.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(workflows): workflow settings, project picker and workflow-aware boards (MUL-7420)

- Settings → Workflows (behind project_workflows_v1): the Default
  workflow plus named workflows, each shown as its step chain with
  handlers and the projects using it; an editor to pick statuses from the
  library, order them, mark the starting step, and set each step's
  handoff, instructions and next/back statuses.
- Project Workflow property: switching dry-runs first and asks where
  issues on unlisted statuses go; nothing else moves and nothing is
  handed off.
- A project board's columns are its workflow's steps, and each column
  head names who entering it hands the issue to.
- Status pickers offer only the issue's workflow steps and say who each
  hands off to; boards refuse drops onto statuses the dragged card's
  workflow does not list.
- New issues in a workflow project start on its starting step, with a
  hint naming who they will be handed to.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(workflows): name built-in steps once in the brief; roomier picker hints (MUL-7420)

Built-in statuses have no stored name, so the handoff brief printed
`todo` todo. Print the key alone when the name adds nothing.

The status picker widens for workflow projects and shows the handoff as
an arrow and name, so step names no longer truncate to one letter.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(workflows): handoff previews and a single timeline entry per handoff (MUL-7420)

- GET /api/issues/{id}/workflow-handoff?status= says what moving an issue
  would do: who it goes to, which runs of its current agent are still
  active, and the brief the handler receives. Backs the confirmation.
- POST /api/issue-workflows/preview-brief renders a step's brief from an
  unsaved workflow, for the editor's preview.
- Mapping plans list the statuses that stay, so the switch dialog can say
  which issues are untouched.
- A handoff is recorded on the status_changed activity (workflow, handler,
  whether a run started, the brief) instead of a separate assignee row.
- The brief now reads like the one shown in the design: the workflow, the
  current step, its instructions, the statuses, and the commands to hand
  off.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(workflows): rebuild the workflow UI to the reviewed design (MUL-7420)

The first cut used generic controls where the design had purpose-built
ones. This brings every surface up to the mockup:

- Settings → Workflows is one page: the workflows (step chain with
  handler avatars, handoff count, projects using it; Default first) and,
  below, the shared status library with each status's workflow usage.
- The workflow editor is a full page instead of a dialog: draggable step
  cards summarising each handoff, and a panel for the selected step —
  swap its status, pick the handler with a segmented control and a
  searchable target, handoff instructions, next/back statuses, a preview
  of the brief the handler receives, duplicate and delete.
- The project Workflow picker lists each workflow's statuses, handoffs and
  projects, links to settings, and always confirms the switch with what
  moves and what stays.
- Moving an issue into a handoff step (status picker, table, board drop)
  asks first: who it goes from and to, a default-on option to stop the
  previous agent's run, and the brief.
- Status pickers name the workflow and project, show each step's handler
  with an avatar or that the assignee stays, and explain themselves.
- Boards: the handler chip in project column heads, hatched columns a
  dragged card cannot enter with the reason, and a drop hint naming who
  the card will go to.
- The timeline records a handoff under its status change, with the brief
  the run received; the issue sidebar shows the workflow it follows.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(workflows): word a queued previous run as queued in the handoff confirm

The confirm dialog said the previous agent was "still running (for N
minutes)" even when its task had only been queued. Queued runs now read
as queued, and the checkbox cancels them instead of stopping them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(workflows): refuse a run's status change once the issue left its step

A run decides where the issue goes next from the step it was queued for.
When someone else moved the issue while the run worked, applying that
decision undid their move: in the live Grok run, a PM parked NW-1 as
blocked and the still-running agent moved it straight back to code
review.

Tasks now record the issue's status when they are queued
(agent_task_queue.workflow_step, written by the task INSERTs; automatic
retries keep their parent's step), and a run's own status changes move
it along. In a workflow project, an agent status change from a run whose
step no longer matches the issue is refused with 409
workflow_step_moved, naming where the issue is now and who moved it, so
the agent stops instead of retrying.

A handoff away from a squad now also stops the runs made on the squad's
behalf, and stopped runs record who stopped them.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(workflows): keep every status entry point inside the issue's workflow

The issue context menu listed every catalog status and applied handoff
steps without the confirmation the status picker and board show, so a
right-click could pick a status the workflow refuses or start an agent
unasked. It now offers the workflow's steps and opens the handoff
confirmation through a shared modal.

One-click status actions no longer assume built-in statuses a workflow
may not list: "mark done" lands on the workflow's done step (hidden when
it has none), un-marking a duplicate returns to the workflow's start,
and "mark duplicate" is offered only when the workflow lists cancelled.
The batch toolbar offers a single project's workflow statuses when the
selection sits in one project.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(workflows): keep issue wakeups consistent with workflow steps

Three places where project workflows and issue wakeups met:

- A wakeup run is queued when it fires, so it took the issue's status at
  that moment as its workflow step. A wakeup an agent registered then
  bypassed the stale-step refusal: parked by a PM, the issue could be
  moved on by the agent's "check CI, then hand to review" wakeup. It now
  keeps the registering run's step; a member's wakeup still starts from
  the issue's status.
- Retries carried handoff_note only for wakeup runs, so a retried
  workflow handoff lost its brief. Rows with a workflow step keep it.
- Switching or editing a workflow remaps statuses in bulk without the
  close rule, so an issue remapped into a done status kept its wakeups
  and pending wakeup runs. The remap now ends them like any status write.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(wakeups): don't also wake the agent a write hands the issue to

A workflow handoff or an assignment already starts the receiving agent's
run. If that agent also subscribed an event wakeup to the issue's status
or assignee, the same write queued a second run for one fact, and
workflows made such writes frequent.

The write now names the agent it hands the issue to in the
transaction-local multica.handed_to_agent (the new agent assignee, a
squad's leader, or a step's handler even when it already owned the
issue), and wakeup capture skips that agent's subscriptions for the
write's issue.* events. Other agents' wakeups, and task, comment and
reaction events, are captured as before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(workflows): tell agents not to watch workflow issues with wakeups

The handoff brief and the runtime's project workflow section now say
that each step's handler gets a run when the issue enters the step, so
wakeups on a workflow issue are for waiting on time or on something
outside it, not on its status, assignee or runs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(workflows): refresh project usage after project changes

Co-authored-by: multica-agent <[email protected]>

* feat(workflows): MUL-7420 split columns from per-project handoffs

Workflows now only define a board's columns between a fixed head
(backlog, todo) and tail (blocked, done, cancelled). Handoffs move to
project.workflow_handoffs, so projects can share columns and still hand
off to different people. Default becomes a real, editable row that every
project without a named workflow follows. Adds project handoff, add-column
(project fork or shared) and project brief preview endpoints; same-agent
adjacent columns re-run the agent.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(core): MUL-7420 project handoffs and Default workflow client model

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(views): MUL-7420 column-header handoffs and project workflow menu

Board column headers set the project's handoff for that column; the
project toolbar gets a workflow menu (full flow, switch, templates) and an
add-column action. Views outside a project show Default's columns, with
other statuses reachable through a status filter. Adds the issue flow card,
create-project workflow choice, a columns-only workflow editor and a
lighter status picker.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(views): MUL-7420 wrap create-project workflow label in a menu group

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(workflows): MUL-7420 keep workflow structure in settings; no squad handoffs

Projects no longer add columns or create workflows: the add-column
endpoint is removed and creating a workflow is admin-only. Handoffs go
to one agent, a member or the project lead; squads route work themselves,
so a handoff to one is rejected, as is the issue-creator handler.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* refactor(core): MUL-7420 drop the project add-column client

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(views): MUL-7420 simpler handoff picker and workflow editor form

The handoff form is one searchable "Hand to" picker (no handoff, the
project lead, an agent or a member); the next status defaults to the next
column and next/back overrides sit under "More options". The workflow
editor gets labelled name and description fields and shows the fixed
statuses as one start and one end row. Templates move from the project
menu to Settings → Workflows → New, and the project board loses its
add-column action.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* refactor(server): MUL-7420 keep handoffs on the workflow, make backlog and blocked optional

Handoffs move back onto workflow steps: a workflow is the whole pipeline,
and a project only picks one. The project lead stays a relative handler, so
projects can still share a workflow. Handoff edits are admin-only, through
the workflow write or PUT /api/issue-workflows/{id}/handoffs/{status}; the
brief preview moves to the workflow too. Drops the unreleased
project.workflow_handoffs migration.

Todo, done and cancelled are required; backlog and blocked are optional and
keep their fixed places. Without blocked, the brief points agents at the
step's back status.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: Cursor <[email protected]>

* feat(views): MUL-7420 edit handoffs on the workflow, optional backlog and blocked

The workflow editor sets each column's handoff in its side panel, and
Backlog and Blocked switch on and off. The board's column popover and the
flow panel edit the workflow's handoff, say how many projects it reaches,
and are read-only for non-admins. Drops the per-project handoff client
code.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(wakeups): MUL-7420 keep platform rules on writes that hand off nobody

Migration 579 skips an issue.* event for the agent a write hands the issue
to. Its filter compared agent_id with the transaction's handed_to_agent
setting using IS DISTINCT FROM, which is false when both are NULL. Main's
platform rules (child_done) have no agent_id, so every issue.* event that
handed the issue to nobody was dropped for them: a parent leaving backlog
never woke its agent for sub-issues that closed meanwhile
(TestChildDoneHeldWhileParentInBacklog).

Rules without an agent are never the handoff target, so they always
capture. Extends TestHandoffDoesNotAlsoWakeTheHandler with a platform rule
on a handoff and on a plain status change; the latter fails on the old
filter.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(views): MUL-7420 three-way handoff choice; quieter move confirm

The handoff form picks one of three directions, as in the mockup: no
handoff, hand to an agent (starts a run), or hand to a member, project lead
included (assignee only). What the step does, where it goes next and the
back status only reach an agent's brief, so they show and save for agent
handoffs alone.

The board's move confirmation keeps the agent's brief collapsed. Create
project shows the workflow picker even when Default is the only workflow,
with a note on where to make more. The hidden-columns panel says a board's
columns come from its workflow, with a link to manage workflows for admins.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(workflows): confirm handoff on project move, per-project handoff copy, two-pane editor (MUL-7420)

- A single issue moved into a workflow project from issue detail confirms
  the handoff of the column it lands on; the write opts in with
  enter_workflow_step. Batch, API and CLI moves stay administrative.
- The handoff preview takes project_id to preview such a move.
- Saving a shared workflow's handoff from a board can keep the change to
  that project: the server copies the workflow for it and switches the
  project in one transaction (copy_for_project_id).
- The column popover says which workflow it edits and who else uses it.
- The workflow editor is a step list with a detail panel for the
  selected step.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* style: gofmt workflow tests

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(views): MUL-7420 show next/back always; edit workflow name and description in place

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(views): MUL-7420 trim copy on the workflows settings page

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix: MUL-7420 keep pre-workflow behavior for workspaces that never adopt workflows

The run step guard now applies only to workflows that hand off, and the
standalone Statuses settings page keeps its original header.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(views): MUL-7420 use a solid placeholder tone on the workflow header

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(workflows): keep the runtime brief cache-stable; fix project board and batch status gaps (MUL-7420)

- Move the project workflow out of the runtime brief into the per-turn
  prompt so CLAUDE.md stays byte-identical across steps and issues.
- An explicit status filter overrides the project workflow's columns; the
  hidden-columns panel lists statuses outside the workflow.
- Batch status offers only statuses every selected workflow allows; a
  partial batch rolls back, refetches and warns.
- setProjectWorkflow returns void instead of an unparsed body.

Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: Lambda <[email protected]>
Co-authored-by: Cursor <[email protected]>
2026-10-16 16:49:49 +08:00
86020b88a4 MUL-8047: chore(server): remove the pre-v3 desktop onboarding shim (#9291)
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-16 16:41:46 +08:00
Jiayuan Zhangandmultica-agent 231dedb552 MUL-8034: chore: clean historical comments across the codebase (#9274)
* chore: clean historical comments (MUL-8034)

Co-authored-by: multica-agent <[email protected]>

* docs: preserve pending Composio expiry contract

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: multica-agent <[email protected]>
2026-10-15 21:54:33 +08:00
8352787263 MUL-7658: fix(daemon): recover undelivered batch claims after timeout (ZIC-260) (#8774)
* fix(daemon): recover undelivered batch claims after request timeout

Co-authored-by: multica-agent <[email protected]>

* test(daemon): avoid copying task service locks in recovery fixture

Co-authored-by: multica-agent <[email protected]>

* test(tasks): isolate delegated recovery assertions (MUL-7658)

Co-authored-by: multica-agent <[email protected]>

* test(daemon): use one clock for deferred claim hints (MUL-7658)

Co-authored-by: multica-agent <[email protected]>

* fix(daemon): keep claim recovery within client deadlines (MUL-7658)

Co-authored-by: multica-agent <[email protected]>

* fix(execenv): preserve racy Git index timestamps in snapshots (MUL-7658)

Co-authored-by: multica-agent <[email protected]>

* fix(daemon): deliver claim responses before recovery side effects (MUL-7658)

Co-authored-by: multica-agent <[email protected]>

* fix(daemon): isolate HTTP claim retries from recovery (MUL-7658)

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: multica-agent <[email protected]>
Co-authored-by: Sol-Boy <[email protected]>
2026-10-15 16:43:24 +08:00
d3c1b44d75 MUL-8014: fix(realtime): revoke workspace WebSocket access (#9249)
* fix(realtime): revoke workspace access on membership removal

Close removed members' sockets, revalidate registration, and gate captured write batches with fresh coalesced membership queries. Cover relay failure, replay, queue races, and transactional removal/rollback (MUL-8014).

Co-authored-by: multica-agent <[email protected]>

* fix(realtime): keep registration free of database waits

Co-authored-by: multica-agent <[email protected]>

* perf(realtime): narrow removal event prefilter

Co-authored-by: multica-agent <[email protected]>

* test(wakeup): avoid global backfill in workspace default test

Co-authored-by: multica-agent <[email protected]>

* fix(realtime): bound concurrent authorization and defer relay revocation

Co-authored-by: multica-agent <[email protected]>

* test(realtime): check caller budget without waiting for expiry

Co-authored-by: multica-agent <[email protected]>

* fix(realtime): decouple member-list invalidation from revocation

Co-authored-by: multica-agent <[email protected]>

* perf(realtime): skip removal decoding for member actor metadata

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Sol-Boy <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-15 12:36:00 +08:00
Frezc 0a979b1f9c fix(search): classify client cancellation separately from server errors (MUL-7989) (#9209)
* fix(search): classify client cancellation separately from server errors

* ci: rerun checks for search cancellation fix
2026-10-13 14:38:36 +08:00
c6b451c9c1 fix(views): route plain attachment download links through the re-sign path (MUL-7991) (#9211)
* fix(views): route plain attachment download links through the re-sign path (WK-11)

A plain markdown link to the API-host attachment endpoint
(MULTICA_PUBLIC_URL/api/attachments/<id>/download, persisted by
buildMarkdownURL in private-storage deployments) opened with a bare
navigation. The browser sent no credential there — web's session cookie is
host-locked to the API origin and SameSite=Strict, and Desktop/mobile carry a
bearer token no <a> can send — so the request returned 401
"missing authorization" and the user downloaded the error body, not the file.

File-card/image and the download button already route through
useDownloadAttachment, which refreshes authenticated metadata and navigates to
the credential-bearing URL the server mints per click (capability / presign /
CloudFront). Plain links missed that path. Intercept attachment download URLs
(UUID-guarded via attachmentIdFromDownloadURL) in RichLink and hand the
click to the same download hook, so all three attachment entry points behave
alike. Modified clicks still open natively; the raw URL remains the durable ref.

Add a routing test covering the UUID-link interception and the non-UUID
non-interception.

Fixes WK-11.

* fix(attachments): scope Markdown downloads to their deployment and workspace

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Dev (multica) <agent@multica>
Co-authored-by: Yushen <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-13 14:23:55 +08:00
Multica Eveandmultica-agent 432eb8eda7 fix(skills): restore legacy issue skill redirect for pre-merge daemons (MUL-7985) (#9213)
Partially reverts #9046. The backend again ships the
multica-working-on-issues redirect stub to daemons that do not
advertise platform-skill-v1, on both inline and skill-ref claims and in
bundle resolution. Released daemons (e.g. v0.4.39) hardcode that skill
name in their brief, and a backend deploy cannot rewrite it.

The daemon-side cleanup from #9046 is kept: current daemons advertise
platform-skill-v1 and point only at multica-platform.

Co-authored-by: multica-agent <[email protected]>
2026-10-13 12:42:39 +08:00
756cdd915c MUL-7794 fix(wakeups): surface recoverable PR condition diagnostics [ZIC-302] (#9066)
* fix(wakeups): surface recoverable PR condition diagnostics

Co-authored-by: multica-agent <[email protected]>

* fix(wakeups): preserve condition diagnostic ownership

Co-authored-by: multica-agent <[email protected]>

* test(wakeups): isolate receipt cleanup limit

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: multica-agent <[email protected]>
Co-authored-by: Yushen <[email protected]>
2026-10-12 13:15:46 +08:00
2penheimerandCursor 41c9bbafec fix(projects): preserve omitted fields in concurrent updates (#9111)
Co-authored-by: Cursor <[email protected]>
2026-10-12 12:47:28 +08:00
e7742b052b MUL-7776: fix(api): stop returning raw error text in 500 responses (#8905)
* fix(api): stop returning raw error text in 500 responses

39 handlers answered a 500 with the message followed by err.Error(),
sending database and store errors (constraint names, SQL states, Redis
errors) to API clients, while the other 720 500 responses in the handler
package send a fixed message. These now send their fixed message too and
log the error with the request's attributes, as the handlers that already
logged it do.

* fix(api): drop raw error text from the task callbacks and skill import 500s

completeTask and failTask sent err.Error() as the whole 500 message, and
the structured skill import put it in the result's Reason on its three
500s. They now send fixed messages and log the error. The task callbacks
keep their 500, so the daemon still retries them: isTransientError reads
the status code, never the body.

* fix(api): close remaining raw error leaks

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Yushen <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-10 12:17:42 +08:00
苗大 3bacc41f84 MUL-7573 fix(lark): clear the Typing reaction without trusting in-process state (#8656)
* fix(lark): persist and reconcile typing cleanup across replicas

- preserve terminal trigger targets and per-input cleanup ownership
- reconcile late Adds and source deletion without delaying replies
- recover interrupted ledger indexes and cover lifecycle boundaries

Refs: #8655

* fix(lark): restrict typing cleanup to the current application

- match both app operator type and the current App ID
- preserve foreign-app, human and unknown-owner reactions
- cover ownership filtering in fast and durable cleanup paths

Refs: #8655

* fix(lark): bound typing recovery and credential retention

- recover taskless indicators after lost debounce timers
- cap workspace ledger admission and redact terminal credentials
- isolate maintenance budgets and test retention and quota boundaries

Refs: #8655

* docs(lark): document typing cleanup recovery and rollout limits

- explain ownership, retention, quota and observable terminal outcomes
- document migration upgrades and safe application rollback

Refs: #8655

* fix(deps): require patched Go 1.26.9 and x/net 0.60

- Require the patched Go standard library to clear reachable vulnerabilities.
- Upgrade x/net to the matching HTTP/2 security release.
- Align local development prerequisites with the minimum Go version.
2026-10-08 20:20:15 -07:00
Bohan Jiangandmultica-agent 647b18b18a fix(search): recognize digit-containing issue prefixes across search paths (#9101)
Co-authored-by: multica-agent <[email protected]>
2026-10-07 19:33:57 -07:00
bb14e8763c feat(issues): add multi_text and multi_url custom property types (MUL-7496) (#8559)
* feat(issues): add multi_text and multi_url custom property types

Workspaces could only hold list-shaped values as preset option lists
(multi_select) or member references (multi_actor). There was no way to
keep a list of free-form strings or links on an issue, so teams with
"related docs / spec links / aliases" needs had to either cram them into
one text field (no structure, nothing clickable) or burn one select
option per link.

This adds two property types:

- multi_text: an ordered list of free-form text entries
- multi_url: an ordered list of http(s) URLs

Each entry is validated exactly like a single text / url value
(length caps, http(s)-only for URLs), duplicates are dropped, insertion
order is preserved, and values are capped at 20 entries to respect the
16KB properties-bag budget. Values ride the existing jsonb bag, so the
jsonb_path_ops GIN index, the @> containment filter, and the client
value schema all keep working unchanged.

Filtering matches any single list element exactly (equality) plus "No
value" — the containment form the filter compiler already emits for
array elements, so no new server predicate is needed. List types
deliberately get no sort order and cannot group, like the other
multi-value types.

On the web, list values render as chips; multi_url chips are clickable
and open in a new tab, and the editor popover offers a per-entry open
button, so several related documents or links stay directly reachable
from the issue. The filter menu reuses the scalar input (equality-only
plus "No value"). CLI: comma-separated --value form for both types,
element-equality --property filtering, and display_values rows.

Server-side allowlist widened by migration 500 (check constraint
re-added NOT VALID then validated, same pattern as migration 341).

* fix(properties): address list property review nits

Co-authored-by: multica-agent <[email protected]>

* fix(properties): scope list drafts to the edited issue

Co-authored-by: multica-agent <[email protected]>

* test(agent): avoid fork races when writing zeroclaw fixtures

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Bohan-J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-07 13:33:02 +08:00
b4ca5b4a23 refactor(skills): retire legacy issue skill redirect (#9046)
Co-authored-by: Multica Eve <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-03 22:48:20 -06:00
d36ef1d1bd perf(issues): scope working-agent facet tasks by visible agents (MUL-7838) (#9004)
* perf(issues): scope working-agent facet tasks by visible agents

Co-authored-by: multica-agent <[email protected]>

* perf(issues): join working facet agent candidates as a relation

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Yushen <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-10-01 17:47:46 +08:00
92649de29c fix(wakeup): scope test scheduler passes to their own workspaces (#8901)
CI runs every backend package concurrently against one database, and
IssueWakeupService.Tick dispatches every ready wakeup in it. The handler
child_done tests' runWakeupTick therefore dispatched rules owned by
concurrently running service tests, consuming their pending receipts
mid-assertion: TestIssueWakeupPendingEventsAreBoundedAndLegacyInputsDrain
failed on main with "unbounded pending receipts: 1" (reproduced locally
2/40 with the handler tests running alongside, 0/20 alone).

Give ListReadyWakeups an optional workspace filter and add
TickWorkspaces for tests; production Tick passes no filter and is
unchanged. Scope the handler helper, the busy-rule test and the expiry
candidate check to the workspaces they own.

Co-authored-by: J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-27 22:43:43 +08:00
3d2592a9a2 MUL-7754: feat: local search index for web and desktop (#8891)
* feat(server): add a change log and sync endpoints for local search (MUL-7754)

Web and Desktop will keep a per-workspace copy of issue, comment, and
project text for instant search. Triggers on issue/comment/project record
the xid of each entity's latest write in search_index_change, covering
every write path including hard and cascaded deletes. Clients bootstrap
with GET /api/search-index/manifest + /snapshot pages and catch up with
POST /api/search-index/changes, whose cursor is a pg_snapshot: a change is
returned once it is visible in the target snapshot but not in the one the
client holds, so late commits are never skipped and long transactions do
not stall catch-up.

Workspace teardown skips the triggers and clears the table, and an hourly
scheduler job prunes rows older than 30 days; a cursor older than the
prune mark gets 410 and rebuilds.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(core): add a synced local search index for web and desktop (MUL-7754)

Each (user, workspace) gets an IndexedDB copy of its issue, comment, and
project text, kept in a worker (SharedWorker when available, otherwise a
dedicated worker per tab) and searched in memory. Matching, ranking, and
snippets port the server's issue/project search, so local rows have the
server's shape and order.

The worker bootstraps from /api/search-index/manifest + snapshot pages,
catches up through /changes after realtime events, reconnects, focus, and
every five minutes, and resumes an interrupted bootstrap. It has no
credentials of its own: tabs run its requests through their API client.
Local results are served only while the copy is complete and recently
confirmed; otherwise searchIssues/searchProjects fall back to the server.
Workspaces over 500 MB of text, lost access, and expired cursors are
handled by declining, wiping, and rebuilding respectively. Session cleanup
deletes every local index.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* feat(views): search through the local index first (MUL-7754)

The command palette, @mention suggestions, the issue picker, and duplicate
suggestions now call the core local-first search. The palette drops its
300 ms debounce while the local index is serving, since no request leaves
the tab.

An E2E spec checks that palette results match server ranking without any
server search request, follow issues created and deleted through the API,
and that logging out deletes the local copy. .env.example documents the
FF_LOCAL_SEARCH_INDEX kill switch.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(core): close local search gaps found in review (MUL-7754)

- Access loss: deleting, leaving, or being removed from a workspace now
  destroys its local copies right away (worker and tab both delete the
  database, since a full-page relocate may end the worker first). The sync
  hook also prunes every copy outside the user's current workspace list,
  which covers access lost while offline or on another device; copies a
  tab is showing are kept in case the list is stale.
- Memory budget: the engine counts UTF-8 bytes the way the manifest does,
  and the budget is checked on every load, snapshot page, and catch-up
  batch, not only at bootstrap. An over-budget copy is dropped and search
  falls back to the server until the workspace is measured again.
- Frozen tabs: a port the sweep dropped is restored with its workspace the
  next time the tab sends anything, a page restored from the bfcache
  attaches again, and a null local answer clears the tab's "serving" flag
  so the palette goes back to debounced server search.

E2E now also covers being removed from a shared workspace.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-27 22:02:13 +08:00