mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-09 01:46:46 +08:00
A reply posted while the agent already has a queued run in the same thread is folded into that run on the server: the reply becomes the run's trigger and the earlier comment joins its covered set. The run's status does not change, so no task event fired and open issue pages kept the run under the earlier comment, leaving the new reply with no visible queue row. Publish task:coalesced after the merge (and after a delegated-failure merge). Web and desktop already refresh the issue's runs on any task: event, which moves the queued block under the newest comment it answers; mobile now listens for it too. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]>
227 lines
11 KiB
Go
227 lines
11 KiB
Go
package protocol
|
|
|
|
// Event types for WebSocket communication between server, web clients, and daemon.
|
|
const (
|
|
// Issue events
|
|
EventIssueCreated = "issue:created"
|
|
EventIssueUpdated = "issue:updated"
|
|
EventIssueDeleted = "issue:deleted"
|
|
EventIssueMetadataChanged = "issue_metadata:changed"
|
|
EventIssueAttachmentsChanged = "issue_attachments:changed"
|
|
|
|
// Comment events
|
|
EventCommentCreated = "comment:created"
|
|
EventCommentUpdated = "comment:updated"
|
|
EventCommentDeleted = "comment:deleted"
|
|
EventCommentResolved = "comment:resolved"
|
|
EventCommentUnresolved = "comment:unresolved"
|
|
EventReactionAdded = "reaction:added"
|
|
EventReactionRemoved = "reaction:removed"
|
|
EventIssueReactionAdded = "issue_reaction:added"
|
|
EventIssueReactionRemoved = "issue_reaction:removed"
|
|
|
|
// Agent events
|
|
EventAgentStatus = "agent:status"
|
|
EventAgentCreated = "agent:created"
|
|
EventAgentArchived = "agent:archived"
|
|
EventAgentRestored = "agent:restored"
|
|
|
|
// Task events (server <-> daemon).
|
|
// Each event maps to a status transition on agent_task_queue. Front-end
|
|
// subscribes by `task:` prefix and invalidates the workspace task
|
|
// snapshot, so the granularity here is "what does the user want to see
|
|
// change" — not "every internal status flip".
|
|
EventTaskQueued = "task:queued" // ∅ → queued (enqueue / retry create)
|
|
EventTaskCoalesced = "task:coalesced" // status unchanged (a comment folded into a run not yet claimed, which now answers it)
|
|
EventTaskDispatch = "task:dispatch" // queued → dispatched (daemon claim)
|
|
EventTaskRunning = "task:running" // dispatched → running (daemon started)
|
|
EventTaskWaitingLocalDirectory = "task:waiting_local_directory" // dispatched → waiting_local_directory (daemon parked on a busy local_directory path)
|
|
EventTaskProgress = "task:progress"
|
|
EventTaskCompleted = "task:completed" // running → completed
|
|
EventTaskFailed = "task:failed" // running → failed
|
|
EventTaskMessage = "task:message"
|
|
EventTaskCancelled = "task:cancelled" // * → cancelled
|
|
|
|
// Inbox events
|
|
EventInboxNew = "inbox:new"
|
|
EventInboxRead = "inbox:read"
|
|
EventInboxUnread = "inbox:unread"
|
|
EventInboxArchived = "inbox:archived"
|
|
EventInboxUnarchived = "inbox:unarchived"
|
|
EventInboxBatchRead = "inbox:batch-read"
|
|
EventInboxBatchArchived = "inbox:batch-archived"
|
|
|
|
// Workspace events
|
|
EventWorkspaceUpdated = "workspace:updated"
|
|
EventWorkspaceDeleted = "workspace:deleted"
|
|
|
|
// Member events
|
|
EventMemberAdded = "member:added"
|
|
EventMemberUpdated = "member:updated"
|
|
EventMemberRemoved = "member:removed"
|
|
|
|
// Subscriber events
|
|
EventSubscriberAdded = "subscriber:added"
|
|
EventSubscriberRemoved = "subscriber:removed"
|
|
|
|
// Activity events
|
|
EventActivityCreated = "activity:created"
|
|
|
|
// Skill events
|
|
EventSkillCreated = "skill:created"
|
|
EventSkillUpdated = "skill:updated"
|
|
EventSkillDeleted = "skill:deleted"
|
|
|
|
// Chat events
|
|
EventChatMessage = "chat:message"
|
|
EventChatDone = "chat:done"
|
|
// EventChatQuickActions supplements a finished turn with follow-up
|
|
// suggestions generated by the daemon's post-completion pass. Sent after
|
|
// chat:done; clients that don't know it simply never resolve the
|
|
// (equally unknown) pending state.
|
|
EventChatQuickActions = "chat:quick_actions"
|
|
// EventChatCancelFinalized carries the deferred outcome of a cancelled
|
|
// chat task once the daemon has flushed its transcript (or the sweeper
|
|
// grace period expired): either a late "Stopped." assistant message or a
|
|
// draft restore (#5219). Channel outbounds (Slack/Lark) deliberately do
|
|
// not subscribe to it — cancellation stays silent on external channels.
|
|
EventChatCancelFinalized = "chat:cancel_finalized"
|
|
EventChatSessionCreated = "chat:session_created"
|
|
EventChatSessionRead = "chat:session_read"
|
|
EventChatSessionDeleted = "chat:session_deleted"
|
|
EventChatSessionUpdated = "chat:session_updated"
|
|
|
|
// Project events
|
|
EventProjectCreated = "project:created"
|
|
EventProjectUpdated = "project:updated"
|
|
EventProjectDeleted = "project:deleted"
|
|
EventProjectResourceCreated = "project_resource:created"
|
|
EventProjectResourceUpdated = "project_resource:updated"
|
|
EventProjectResourceDeleted = "project_resource:deleted"
|
|
|
|
// Label events
|
|
EventLabelCreated = "label:created"
|
|
EventLabelUpdated = "label:updated"
|
|
EventLabelDeleted = "label:deleted"
|
|
EventIssueLabelsChanged = "issue_labels:changed"
|
|
|
|
// Custom property events. Definitions are archived, never deleted, so
|
|
// there is no property:deleted — archive arrives as property:updated.
|
|
EventPropertyCreated = "property:created"
|
|
EventPropertyUpdated = "property:updated"
|
|
EventIssuePropertiesChanged = "issue_properties:changed"
|
|
|
|
// Issue status catalog events (MUL-6243). ONE event for all four writes
|
|
// — create, edit, archive, reorder — rather than a verb per write, because
|
|
// the catalog is read as a whole table and every client answers all four
|
|
// the same way: re-read it. Splitting it would mint four contracts that no
|
|
// consumer can tell apart, and reorder has no single row to name anyway.
|
|
// The `action` in the payload is advisory (it makes a frame in devtools
|
|
// self-describing); nothing routes on it.
|
|
EventIssueStatusChanged = "issue_status:changed"
|
|
|
|
// Project workflow events (MUL-7420). Same shape as the status catalog
|
|
// event: workflows are read as one list, so every write answers the same
|
|
// way — re-read it. A project switching workflows publishes
|
|
// project:updated instead.
|
|
EventIssueWorkflowChanged = "issue_workflow:changed"
|
|
|
|
// Pin events
|
|
EventPinCreated = "pin:created"
|
|
EventPinDeleted = "pin:deleted"
|
|
EventPinReordered = "pin:reordered"
|
|
|
|
// Invitation events
|
|
EventInvitationCreated = "invitation:created"
|
|
EventInvitationAccepted = "invitation:accepted"
|
|
EventInvitationDeclined = "invitation:declined"
|
|
EventInvitationRevoked = "invitation:revoked"
|
|
|
|
// Autopilot events
|
|
EventAutopilotCreated = "autopilot:created"
|
|
EventAutopilotUpdated = "autopilot:updated"
|
|
EventAutopilotDeleted = "autopilot:deleted"
|
|
EventAutopilotRunStart = "autopilot:run_start"
|
|
EventAutopilotRunDone = "autopilot:run_done"
|
|
|
|
// Squad events
|
|
EventSquadCreated = "squad:created"
|
|
EventSquadUpdated = "squad:updated"
|
|
EventSquadDeleted = "squad:deleted"
|
|
|
|
// Daemon events
|
|
EventDaemonHeartbeat = "daemon:heartbeat"
|
|
EventDaemonHeartbeatAck = "daemon:heartbeat_ack"
|
|
EventDaemonRegister = "daemon:register"
|
|
EventDaemonTaskAvailable = "daemon:task_available"
|
|
EventDaemonTaskSupplementAvailable = "daemon:task_supplement_available"
|
|
EventDaemonRuntimeProfilesChanged = "daemon:runtime_profiles_changed"
|
|
EventDaemonWorkspacesChanged = "daemon:workspaces_changed"
|
|
// EventDaemonPendingWork is a runtime-scoped hint that a heartbeat-carried
|
|
// request (model discovery, capability discovery, or local-skill import) is
|
|
// queued for that runtime. Without it the daemon only learns about the
|
|
// request on its next scheduled heartbeat, adding up to one HeartbeatInterval
|
|
// (15s by default) of dead wait to an interactive UI flow (MUL-5444). The
|
|
// hint carries no work
|
|
// itself: the daemon still pulls the request through the normal heartbeat
|
|
// claim, so a lost or duplicated hint is harmless.
|
|
EventDaemonPendingWork = "daemon:pending_work"
|
|
// Generic daemon→server request/response over the WebSocket control
|
|
// connection (MUL-4257). The daemon sends EventDaemonRPCRequest with a
|
|
// correlation id + method + body; the server replies EventDaemonRPCResponse
|
|
// with the same request id. This is the transport for WS-first claim (with
|
|
// HTTP fallback) and any future daemon→server RPC.
|
|
EventDaemonRPCRequest = "daemon:rpc_request"
|
|
EventDaemonRPCResponse = "daemon:rpc_response"
|
|
|
|
// GitHub integration events
|
|
EventGitHubInstallationCreated = "github_installation:created"
|
|
EventGitHubInstallationDeleted = "github_installation:deleted"
|
|
EventPullRequestLinked = "pull_request:linked"
|
|
EventPullRequestUpdated = "pull_request:updated"
|
|
EventPullRequestUnlinked = "pull_request:unlinked"
|
|
|
|
// VCS integration events (Forgejo / Gitea / GitLab)
|
|
EventVCSConnectionCreated = "vcs_connection:created"
|
|
EventVCSConnectionDeleted = "vcs_connection:deleted"
|
|
|
|
// Lark integration events. `created` covers both first-install
|
|
// (UNIQUE on (workspace_id, agent_id) means at most one row per
|
|
// agent) and re-install via UpsertLarkInstallation — front-ends
|
|
// treat both as a single "installation appeared / refreshed"
|
|
// notification. `revoked` flips status to 'revoked' without
|
|
// deleting the row; the audit trail is preserved.
|
|
EventLarkInstallationCreated = "lark_installation:created"
|
|
EventLarkInstallationRevoked = "lark_installation:revoked"
|
|
|
|
// Slack installation lifecycle (MUL-3666). Same semantics as the Lark
|
|
// events: `created` covers both first install and OAuth re-install (the
|
|
// UNIQUE on (workspace_id, agent_id, channel_type) means at most one row
|
|
// per agent), `revoked` flips status without deleting the row. Front-ends
|
|
// invalidate the Slack installations query on either.
|
|
EventSlackInstallationCreated = "slack_installation:created"
|
|
EventSlackInstallationRevoked = "slack_installation:revoked"
|
|
|
|
// DingTalk installation lifecycle follows the same create/revoke semantics
|
|
// as Slack's BYO channel installation. BindingUpdated shares the
|
|
// dingtalk_installation prefix because it changes the member-scoped account
|
|
// identifiers returned by the installation listing.
|
|
EventDingTalkInstallationCreated = "dingtalk_installation:created"
|
|
EventDingTalkInstallationRevoked = "dingtalk_installation:revoked"
|
|
EventDingTalkAccountBindingUpdated = "dingtalk_installation:binding_updated"
|
|
|
|
// WeCom smart-bot installation lifecycle. Same semantics as Lark /
|
|
// Slack: `created` covers both first install and re-install via
|
|
// UpsertChannelInstallation (the UNIQUE on (workspace_id, agent_id,
|
|
// channel_type) means at most one row per agent); `revoked` flips
|
|
// status without deleting the row. Front-ends invalidate the wecom
|
|
// installations query on either.
|
|
EventWecomInstallationCreated = "wecom_installation:created"
|
|
EventWecomInstallationRevoked = "wecom_installation:revoked"
|
|
|
|
// Telegram installation lifecycle. Same contract as the Slack pair:
|
|
// front-ends invalidate the Telegram installations query on either.
|
|
EventTelegramInstallationCreated = "telegram_installation:created"
|
|
EventTelegramInstallationRevoked = "telegram_installation:revoked"
|
|
)
|