mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-08 17:36:45 +08:00
main
16
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
bb3747186e |
feat: let agents steer running turns they could start (MUL-8285) (#9630)
* feat(server): let agents steer running turns they could start (MUL-8285) An agent comment can now be added to another agent's running turn, the way a member steers from the composer: exactly when the comment could start that agent (canInvokeAgent, judged on the human at the top of the chain), never its own turn. - steer_task_ids now work for agent authors; steer: true asks the server to add the comment to the current running turn of every recipient without a named turn. - Agent-authored bindings are capped per turn (10) and per authoring agent (3). The task row is locked in its own statement before the bind, so the cap count sees what an earlier lock holder committed. - An agent comment that steered a turn but was never read is replayed on completion like the mention it replaced. Member receipts keep today's behaviour (the member sees the failure and can retry or resend). - The supplement claim returns author_type and the agent's name. A daemon that does not send task-supplement-author-v1 gets the attribution in the content, so an agent message never reads as a human's. - can_supplement on task-runs is computed for an agent caller by the same rule. Co-authored-by: Tim Smart <[email protected]> Co-authored-by: WitMiao <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(daemon): present an agent's added message as an agent's (MUL-8285) Claims send the task-supplement-author-v1 capability and render agent messages as peer input that cannot replace the objective or override humans. Co-authored-by: Tim Smart <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(cli): add --steer and --steer-active to issue comment add (MUL-8285) Docs: CLI help, CLI_AND_DAEMON.md, the CLI docs in every locale, the squad leader briefing, and a steering section in the multica-platform mentions reference. Co-authored-by: Tim Smart <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix: close the review gaps in agent steering (MUL-8285) - Replay an unread agent steer even when it reached the turn through an implicit route (a worker's reply to its leader): a comment bound to the completing run counts as planned input for the replay filter. - Drop the receipt of an agent steer once completion has replayed it, so no client (old or new) offers to resend what the follow-up already handles. - Never hand an agent's message to a daemon without task-supplement-author-v1: it would frame it as a human's. The message stays pending, settles when the turn ends, and is replayed as a mention. - Tell a squad leader about --steer only when its daemon advertises cli-comment-steer-v1, since an older CLI rejects the flag; the skill reference says to drop the flag on 'unknown flag'. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Tim Smart <[email protected]> Co-authored-by: WitMiao <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
e2f4a22030 |
feat: steer a running agent from the normal composer, per recipient (MUL-7631) (#8760)
* feat(server): steer running turns from ordinary comments (MUL-7631) A comment can now name recipients whose running turn should receive it (`steer_agent_ids` on POST /issues/:id/comments). Each named recipient with a running, steer-capable turn gets the comment bound to that turn instead of a follow-up run; any other recipient keeps its normal queued / coalesced / deferred trigger, so losing a race never drops input. - One comment can steer several turns: receipts are keyed by (comment_id, task_id) and returned as `supplements` on comments and timeline entries. The single supplement_* fields mirror the first receipt for older clients. - Completion reconciliation skips a steered comment only for the agent it steered, so the same comment still reaches recipients that were addressed without steering. - Delivery claims match the (comment, run) pair. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(issues): choose per recipient how a message reaches a running agent (MUL-7631) Steering moves from a separate "Add message" box on the run row into the normal reply and comment composers. Recipients keep the existing trigger rules; each recipient's current run on the issue decides what the message can do: - running (and steer-capable): add to the current run (default in that run's own thread), start after this run, stop and start over, or skip - queued: include when it starts, or skip - idle: start on send, or skip Receipts follow the message ("Waiting for Lambda to read it" → "Read by Lambda · after step N"), a failed delivery offers retry or sending it as a new run, and the run's step list marks where it read the message. A draft whose recipient finished meanwhile stays in place and says it will start a new run; a message with files is handled after the run. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(server): steer only the chosen turn, and make a retried steer idempotent (MUL-7631) - Steering names the exact running turns the author chose (`steer_task_ids`). A chosen turn that ended before the send arrives is never swapped for a later turn of the same agent; that recipient keeps its normal trigger. - A steering send carries `client_request_id`. A retry after a lost response returns the original comment (200) instead of creating a second comment and a second delivery; a concurrent twin binding the same request is treated as already steered. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(issues): guard steering sends against stale previews and broad resends (MUL-7631) - "Stop and start over" never stops a run while the trigger preview is still catching up with an edited @mention: the stop is dropped unless the preview answers the submitted text. - Steering sends the chosen turns' task ids and one request id per draft text, reused on retries of the same text. - "Send as a new run" on a failed receipt reaches only that receipt's agent: every other current recipient is suppressed. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(issues): keep the reply box placeholder neutral (MUL-7631) The empty reply box cannot know who a reply goes to: the author may @ someone else or only talk to a teammate. The recipient chip already says what Send will do once there is text. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * test(issues): give steering composer tests room for the preview debounce (MUL-7631) Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(issues): choose the default reply to a running agent (MUL-7631) Preferences > Comments gains "When replying to a running agent": "Add to current run" (default) or "Start after this run". It only changes what a reply does without a per-message choice; the recipient chip still offers both, and replies that never steered by default (another thread, an idle or queued agent) are unaffected. Saved on this device next to the sticky comment bar. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(server): save one comment per send and honor "don't start" on replay (MUL-7631) Idempotency now lives on the comment, not the delivery receipt. A send's client_request_id is stored on the comment behind a partial unique index (migrations 550/551), so a retry, or a concurrent twin, returns the saved comment (200) whether the send steered a running turn or fell back to a normal trigger. Before, a twin could save a second comment, and a send whose turn had ended kept no receipt to find. The agents an author chose not to start are stored on the comment too, and completion replay skips them. "Send as a new run" for one missed recipient, and "Won't start this time", no longer wake the other agent once its current run completes. An edit re-records the choice. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(issues): repeat the original steering request on retry (MUL-7631) Until a send is settled, retrying the same text sends its first steering request unchanged, even after the chosen turn ended. The server then returns the comment the first attempt saved instead of posting it again as new input. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(migrations): fail fast on locks in 549 and 550 (MUL-7631) Both take an ACCESS EXCLUSIVE lock on a table the product touches continuously: comment for every read and write, task_supplement for every daemon claim. Bound lock acquisition and execution, as 483 does for issue, so a long transaction makes the migration fail and retry on the next run instead of queueing all traffic behind it. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(server): steer at most one turn per comment during rollout (MUL-7631) Steering is live, and servers from before per-run receipts claim every receipt of a comment at once. While they still serve daemons during a rollout, a comment bound to two turns could have the second copy marked in flight and never delivered. Until every server claims per (comment, run), a comment steers the first chosen turn only; later recipients keep their normal trigger. A follow-up lifts the limit after this ships. Also covers a steer_task_ids entry that names another issue's turn. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(server): finish a saved send on retry; keep an edit's choices with its text (MUL-7631) A send's comment was saved before its agents were started or steered, and deduplication treated the saved comment as the whole send. A dropped connection cancelled the request between the two, and the retry only returned the comment, so no agent ever received it. - Attempts of one logical send now run one at a time under a transaction advisory lock, held on its own connection: a twin waits for the attempt in progress (up to 30s, then 409) instead of racing it, and a process that dies releases it. - After the comment is saved, dispatch runs on a non-cancelable context, and comment.client_request_dispatched_at (migration 552) records that it finished. A retry that finds a saved but undispatched comment finishes the dispatch with the comment's recorded choices. An edit's "don't start" choices were written by a separate statement after the text, so interleaved edits could leave an older edit's choice. They are now part of UpdateComment, under the same revision check as the text. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(issues): offer one steered recipient per message during rollout (MUL-7631) The server steers at most one turn per comment until #8800, but the composer showed "Add to current run" for every running recipient and the server quietly queued the rest. Now only one recipient steers: the one the author picked, otherwise the first that would by default. The others show "Start after this run", and picking "Add to current run" on one of them moves the steer there. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(server): claim a send before dispatching it; drop the request lock (MUL-7631) The previous recovery marked a send dispatched only after reaching its agents, so a failure after dispatch made a retry reach them all again. It also held a pooled connection per send for an advisory lock while the send needed more from the same pool, which could wait on itself until timeout. A send's comment now holds a dispatch claim instead (comment.client_request_dispatched_at, one conditional update): only the attempt whose claim takes effect publishes the comment and reaches its agents. A failed claim writes nothing else and returns 500 for the client to retry; a retry that finds an unclaimed comment claims and dispatches it; a claimed one is only returned. Concurrent attempts no longer wait on each other, and post-save dispatch keeps a 30s bound of its own. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(server): let one comment steer several running turns (MUL-7631) Lifts the rollout limit from #8760. Every server now claims receipts per (comment, run), so a comment can be bound to each running turn its author chose, and each recipient receives it in its own run. Merge only after #8760 is deployed everywhere. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(issues): steer every running recipient a message addresses (MUL-7631) The client side of lifting the rollout limit: each running recipient can take the message in its current turn again, instead of only one per message. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * refactor(issues): drop send-retry idempotency and two display extras (MUL-7631) A steering send now behaves like any other comment when its response is lost: a retry posts it again. The machinery that tried to make that retry return the saved comment grew with every edge it met, and ordinary comments never had it: - Remove comment.client_request_id, its unique index, and the dispatch claim (migrations 551/552, and 550 reduced to suppressed_agent_ids). - Remove the replay/resume path in CreateComment, the post-save non-cancelable dispatch context, and the client's sticky request id. Steering sends only `steer_task_ids`; each binding uses the comment id as its receipt request id. Two display extras go too, since the UI already shows the same thing: - The "run ended while you were writing" notice: the recipient chip already switches from "Add to current run" to its new action. - "after step N" on a read receipt: the run trace already places the message at the step where it was read. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(issues): space agent names in Chinese copy; neutral recipients header (MUL-7631) - The steer badge joined names with Intl.ListFormat, which gives "Lambda和Orion" in Chinese. Latin names are now spaced from the Chinese joiner ("Lambda 和 Orion"), per the zh copy conventions; the enumeration comma stays unspaced. - The multi-recipient popover was still titled "Will start when sent" and its tooltip said "choose who runs", though each row now picks how that agent handles the message. Title it "Recipients" and reword the tooltip. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(issues): translate the recipient source labels in Chinese (MUL-7631) "assignee" and "@mention" were left in English in zh-Hans; the recipient menu header shows them. Use 负责人 (per the zh conventions) and @提及. 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]> |
||
|
|
765321f053 |
MUL-6951 fix(autopilot): carry the trigger creator's authorization on scheduled runs (#7918)
* fix(autopilot): carry the trigger owner's authorization on scheduled runs A schedule/webhook autopilot run left originator_user_id NULL, so canInvokeAgent saw no human and fail-closed against private agents. Arming a trigger is the same authorizing act as clicking "run now" — deferred, not absent — so withholding the originator did not remove that authority. It only forced every capability the automation legitimately needed to arrive as a separately-bounded borrow path: MUL-4857 for child creation, MUL-6691 for the run's own issues, #7902 for the accountable member's issues, with sub-issue dispatch queued up as the fourth. Each carries a hand-written scope that no operator can see in the UI. attribution.TriggerOwner / RuleOwner now populate UserID (the originator) instead of only the accountable side. finalizeAttribution mirrors it, so the originator == accountable invariant that migrations 190/197 enforce holds without a schema change or backfill. Both autopilot execution modes resolve through triggerOwnerAttribution, so run_only and create_issue are covered by the same two lines. This grants exactly the trigger owner's own rights — canInvokeAgent is untouched, only the principal handed to it changes — and originator_source still distinguishes a scheduled fire from a manual one in the audit trail. The borrowed-authority machinery in handler/agent_access.go is now dead for newly enqueued runs but is deliberately left in place: tasks enqueued by the previous backend are still running during a rolling deploy and carry originator NULL, and they must keep the path they were admitted under. Deleting it is a follow-up once this is deployed. Composio connected-app credentials also key on originator_user_id, so scheduled runs now receive the trigger owner's connected apps. Decoupling that from authorization was considered and explicitly deferred. Verification (real PostgreSQL 17): - internal/attribution, internal/service, internal/handler: PASS - the five attribution_stamp tests that pinned "originator stays NULL" were updated to assert the new principal, and fail against the old constructors - pre-existing, unrelated failures in internal/daemon/execenv (4 Hermes store-path tests) reproduce identically on a clean tree Co-authored-by: multica-agent <[email protected]> * refactor(autopilot): delete the borrowed-authority machinery With a scheduled run carrying its trigger owner as originator, every autopilot-shaped authority path in agent_access.go is unreachable: the invoke gate resolves a real human from the speaking task and judges it exactly as it judges that human acting directly. Removed: - autopilotTaskAssignAuthority + autopilotAssignAuthorityFromRequest (MUL-6691) - issueBoundToAutopilotTask, taskRunsAutopilotInWorkspace - autopilotDelegationAuthority and its FromRequest / FromComment resolvers (MUL-4857) - effectiveInvocationAuthorityFromRequest - assignAuthorityScope, assignScopeKind and the four scope constructors - commentTriggerComputeOptions.AutopilotDelegationAuthorityUserID + effectiveInvoker() validateAssigneePair and triggerTasksForComment lose their now-meaningless scope / delegation-authority parameters; every caller passes the run's originator instead. This also retires the fourth borrow scope #7902 proposed. CreateIssue still loads the parent issue when assigning, even though the gate no longer needs the row: it is what turns a cross-workspace or missing parent into 400 "parent issue not found" instead of a 403. Covered by a test. Two behaviour notes, both accepted: - Authority now propagates down a delegation chain, because the parent run finally has an originator to copy. Under the old design each hop had to re-borrow. This matches what a manual "run now" already did. - The old borrow path required task.Status == "running"; the originator path does not, and never did for any non-autopilot run. Task tokens are revoked at the terminal transition (TaskService, MUL-2600), so the primary guard remains; this only drops a second layer that existed solely for autopilot runs. Tests: the two suites that pinned the borrow bounds (autopilot-created-issue binding, authored-by-this-task binding) described a mechanism that no longer exists, so they collapse into one file asserting the rule that replaced them — rights / no rights / no human at all, across create-top-level, create-child, update and batch. The mention-authority suite keeps its shape; its fixture now gives the run a human, as a live run has. Verification (real PostgreSQL 17): internal/attribution, internal/service and internal/handler all PASS. Pre-existing unrelated failures in cmd/multica, internal/cli, internal/daemon and internal/daemon/execenv reproduce identically on a clean tree. Co-authored-by: multica-agent <[email protected]> * fix(autopilot): resolve one principal per dispatch and expire it with the run Addresses Elon's review of #7918. Three findings, all confirmed against the code before fixing. P1-1 — admission and the run's identity could be two different people. Automatic dispatch admitted the assignee agent as the AUTOPILOT CREATOR (canCreatorInvokeAgent) while the task took its originator from the trigger. With A owning both the autopilot and a private agent, and B owning the trigger, a dispatch was admitted as A and then executed as B — rights neither holds alone. The old suite could not see it because it called dispatchRunOnly directly, skipping admission. - autopilot_trigger gains an IMMUTABLE created_by (migration 449, best-effort backfill from published_by). published_by keeps its audit meaning and still transfers on a substantive edit; it is no longer an authorization input, because letting an editor move it silently re-authorizes the automation as that editor. Per Bohan's ruling the run always acts as the trigger CREATOR. - ResolveAutopilotTriggerPrincipal is now the single answer to "who does this run act as". Admission, the task stamp and every delegated run read it, so the fork is unrepresentable. It also binds the trigger to its autopilot (GetAutopilotTriggerForAutopilot — the old lookup was WHERE id = $1, which would let a foreign trigger select the principal) and re-checks workspace membership on every dispatch, so removing a member revokes their triggers. - canCreatorInvokeAgent is now unreachable and deleted. - RuleOwner reverts to accountable-only. It is a guess at who owns a rule; promoting it would hand a legacy trigger somebody's rights without that person arming anything. Such runs carry no originator and fail closed. P1-2 — a terminal task could still spend its human's rights. invokeOriginatorFromRequest now refuses completed / failed / cancelled tasks. Token revocation at the terminal transition stays the primary guard, but it is best-effort, explicitly non-fatal on failure, with a 24h expiry behind it. The check sits at the authorization boundary rather than the auth boundary, so a finishing run can still post its final comment but cannot start new work. P1-3 — the PR and code comments described Composio backwards. BuildTaskOverlay ignores the originator entirely and builds from the AGENT OWNER's connections and allow-list (MUL-3963). Naming the originator hands out no third-party credentials. Comments corrected; no decoupling was ever needed. Operational note: a trigger with neither created_by nor published_by (predating migration 186) resolves no principal, so its autopilot stops dispatching until the trigger is re-saved. That is the intended fail-closed behaviour. Tests: full dispatch through admission for both fork directions; principal fails closed on legacy / cross-autopilot / removed-member; terminal task refused on all three statuses; the edit-transfer tests now assert the principal STAYS with the creator while published_by moves. Verified the fork test fails when admission is reverted to canCreatorInvokeAgent. Verification (real PostgreSQL 17, pristine database): internal/attribution, internal/service, internal/handler and cmd/server all PASS. Migration 449 up/down round-trips. Pre-existing unrelated failures in cmd/multica, internal/cli, internal/daemon and internal/daemon/execenv are unchanged. Co-authored-by: multica-agent <[email protected]> * fix(autopilot): bind the trigger principal to its workspace, correct stale docs Second round of Elon's review on #7918. 1. The trigger lookup only enforced half of what its comment claimed. GetAutopilotTriggerForAutopilot matched on (trigger id, autopilot id); the workspace was used only for the creator's membership check afterwards. That check proves the resolved human belongs to the workspace PASSED IN, not that the autopilot does — a member of two workspaces satisfies it while the trigger came from the other tenant. The query now joins autopilot and filters on workspace_id, and the resolver passes it through. Regression test seeds two workspaces sharing one member and asserts the foreign workspace resolves nobody; verified it fails against the pre-fix query. 2. Several permission-boundary comments still described the pre-MUL-6951 model — that a run's authority and its accountable human transfer to whoever last edited the trigger. That is now the opposite of the code, and these are the comments a future reader would use to reason about the authorization model, so they are corrected at all four sites: the trigger create site, the SetAutopilotTriggerPublisher query, triggerOwnerAttribution, and the dispatchRunOnly attribution block. published_by is described as what it now is: a config-audit column with no bearing on a run. 3. The migration comment now states the backfill's imprecision plainly: for an already-edited trigger it freezes the last recoverable EDITOR as the immutable creator, not the historical creator, which the schema never recorded. It also records that a NULL/NULL trigger has no recovery path by decision (Bohan: leave them empty) — re-saving re-stamps published_by, not created_by — so its runs stay refused with a recorded failure_reason. Not done, per Bohan's ruling: no re-save takeover path for legacy triggers and no migration-period alerting. Verification (real PostgreSQL 17, pristine database): internal/attribution, internal/service, internal/handler and cmd/server PASS; full suite shows only the four pre-existing unrelated failures (cmd/multica, internal/cli, internal/daemon, internal/daemon/execenv). go vet ./internal/... and git diff --check clean. Co-authored-by: multica-agent <[email protected]> * docs(autopilot): carry the creator-principal semantics into every description Third round of Elon's review on #7918. Two must-fixes, no behaviour change. 1. The final semantics had not reached every surface that describes them. Migration 189 wrote COMMENT ON COLUMN text for autopilot_trigger.published_by_* saying it decides the accountable human of the runs a trigger fires. That is now false, and because sqlc generates column comments into models.go, the stalest description of the authorization model sat in generated code every reader passes through. 189 is released and untouched; migration 449 overrides both comments and documents created_by_* for the first time, and sqlc is regenerated. 449's down restores 189's original text verbatim so a rollback does not leave MUL-6951 wording describing a model the database no longer runs. The same stale semantics remained in prose at six code sites — the plain and manual dispatch entry points, the scheduled-plan path, the create_issue enqueue comment, the autopilot-level republish path, and the webhook trigger create site. All said a run's authority or accountable human follows the last editor, or that automation resolves rule_owner. These sit on the authorization path, so they are corrected rather than left to drift. published_by is now described as what it is: config audit, with no bearing on a run. Also corrects the backfill comment's migration reference: trigger publishers arrived in 189, not 186. 2. The test file this PR added open-coded INSERT ... RETURNING id with matching t.Cleanup(DELETE ...), which CLAUDE.md prohibits. Rewritten onto testutil.Fixture: a small principalFixture embeds it and every row — members, runtimes, private agents, autopilots, triggers, the second workspace — is built and cleaned up by dbfx. The only remaining DELETE is the mid-test member removal that IS the behaviour under test. Re-verified after the rewrite that both regressions still fail against the old code: reverting admission to the autopilot-creator check reddens the fork test, and dropping the workspace join reddens the cross-workspace test. Verification (real PostgreSQL 17, pristine database): full suite shows only the four pre-existing unrelated failures (cmd/multica, internal/cli, internal/daemon, internal/daemon/execenv). Migration 449 up/down round-trips and the down restores 189's comments. go vet ./internal/... and git diff --check clean. Co-authored-by: multica-agent <[email protected]> * docs(autopilot): state the backfill widening plainly, drop raw-SQL test seeding Fourth round of Elon's review on #7918. Both items are leftovers from the previous round; no behaviour change. 1. Migration 449's backfill comment carried two factual errors. It said published_by_* arrived in migration 186; it was 189. The previous round intended to fix this but the string replacement did not match the wrapped line and silently did nothing, so the wrong number shipped. It also claimed the backfill "is no wider than the authority those runs carry today", which is false: before MUL-6951 an automatic run had no originator at all, only narrowly-scoped borrow paths, so promoting a backfilled editor to the run's full originator IS a widening. The product decision to accept it stands; the comment now says so directly as a compatibility tradeoff instead of denying the widening. A migration comment that understates what it grants is worse than no comment. 2. The new test file still reached raw SQL transitively. It called seedAttributionFixture, which hand-writes INSERT and cleanup for its user / workspace / member / runtime / agent — so the file's own claim that "every row goes through testutil.Fixture" was untrue and the repository's fixture rule was still unmet. It now bootstraps the base identity with Fixture.User / Workspace / Member on an unbound fixture and adopts it, which also drops the agent and issue that helper builds and these tests never use. The comment explains why the helper is deliberately not reused. Re-verified after the fixture change that both regressions still fail against the old code: reverting admission to the autopilot-creator check reddens the fork test, and dropping the workspace join reddens the cross-workspace test. Verification (real PostgreSQL 17, pristine database): full suite shows only the four pre-existing unrelated failures (cmd/multica, internal/cli, internal/daemon, internal/daemon/execenv). go vet ./internal/..., gofmt and git diff --check clean. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
69744241e8 |
fix(comment): restore autopilot @mention delegation authority (MUL-4857) (#5512)
* fix(comment): restore autopilot @mention delegation authority (MUL-4857) A schedule/webhook autopilot run is unattributed by design (no top-of-chain human originator, MUL-4302). Since MUL-3963 the A2A invoke gate (canInvokeAgent) keys on that originator, so a mid-run @agent/@squad delegation on an autopilot-created issue fails closed for the DEFAULT private agent (and member-scoped public_to agents): the mention renders but no run is enqueued. The SAME autopilot's first dispatch is admitted via the autopilot creator (autopilotAdmitInvoke -> canCreatorInvokeAgent), so first-dispatch and mid-run delegation disagreed. Align them: when an unattributed agent/system-authored comment on an autopilot-origin issue reaches computeCommentAgentTriggers with no originator, fall back to the autopilot creator as the effective invoking user for the gate. The gate still runs (no unrestricted agent-to-agent bypass); it is authorization only -- the enqueued task's originator/attribution stays unattributed. Scoped to autopilot-origin issues so other unattributed chains stay fail-closed. Adds a DB-backed regression test covering: creator-owns-target admits, a non-autopilot unattributed run stays denied, and a creator without invoke rights stays denied. Co-authored-by: multica-agent <[email protected]> * fix(comment): bind autopilot @mention authority to verified task lineage (MUL-4857) Address the review's confused-deputy finding on the P0 fix. The first cut keyed the invoke-gate fallback on issue provenance + an empty originator alone (invokeAuthorityForAutopilotIssue took only the issue), so any unattributed run could borrow a stranger autopilot creator's rights merely by commenting on that autopilot's issue — and the fallback also leaked past explicit @mention into the plain-comment squad-leader path and system actors. Rework it so the autopilot-creator authority is granted ONLY when the SPEAKING task's lineage is verified against this issue: - resolve the authority separately (new AutopilotDelegationAuthorityUserID on commentTriggerComputeOptions), never by overwriting OriginatorUserID; the gate reads it through opts.effectiveInvoker() only when no human originator resolved, so attribution stays untouched; - resolve from a server-trusted speaking task — X-Task-ID on create/preview, comment.source_task_id on edit/reconcile — via autopilotDelegationAuthority, which admits only when author == task agent AND task.issue_id == this issue AND the issue is autopilot-origin, then keys on the member autopilot creator; - do NOT key on autopilot_run_id: in create_issue mode (the reported case) the leader task is enqueued through the ordinary issue-assignment path and has no autopilot_run_id — the task.issue_id == issue binding is what proves the run is part of this autopilot's work while rejecting foreign-issue runs. Tests: replace the provenance-only regression with lineage-bound coverage — verified-lineage-admits, creator-without-rights-denied, non-autopilot-denied, missing-source-task-denied, cross-issue-source-task-denied, author!=task-agent- denied — plus an end-to-end CreateComment path asserting the private worker is enqueued and the delegated run stays unattributed. Verified the fallback is load-bearing (positive + e2e fail with it disabled) and the full internal/handler package passes. Skill docs (multica-mentioning) updated to the lineage-bound contract and new helper names. Co-authored-by: multica-agent <[email protected]> * fix(comment): make autopilot @mention authority consistent across defer/edit (MUL-4857) Second review round (Elon) surfaced two must-fixes on top of the lineage binding. 1. Busy-target completion reconcile lost the authority. A delegation to a target that is already running is deferred to that target's completion reconcile (reconcileCommentsOnCompletion). That path recomputed triggers with only the (empty) originator, so an unattributed autopilot delegation's follow-up was gate-denied again and silently dropped. It now restores the delegation authority from comment.source_task_id, so the follow-up fires once the target frees up — still unattributed. 2. Edit could borrow the old authoring run's authority, and preview != save. The edit preview keyed authority on the current request task while save keyed it on the comment's original source_task_id, so an agent editing its old autopilot comment from a task on an UNRELATED issue would fail-closed in preview but reuse the old autopilot creator's authority on save (cross-issue confused-deputy, and a preview/side-effect divergence). Fix: treat source_task_id as the persisted per-action authority lineage and re-stamp it on edit to the CURRENT editing task, issue-scoped exactly like CreateComment. A cross-issue edit re-stamps it to NULL, so preview, save, AND the deferred reconcile all fail closed identically. UpdateComment query gains a source_task_id param (sqlc regen). Also locks the review-accepted behavior that effectiveInvoker() carries the autopilot-creator authority into the plain assigned-squad-leader wake (a worker's result comment on the autopilot issue can still wake the private leader). Tests: reconcile-restores-authority (owns -> one unattributed follow-up; no rights -> none); edit re-stamp (same-issue keeps authority and triggers; cross-issue clears source_task_id and fails closed); worker-result wakes private squad leader. Verified both fixes are load-bearing (each negative control reproduces the exact regression Elon described), full internal/handler + internal/service packages pass, gofmt/vet clean. Skill docs (multica-mentioning) updated. Co-authored-by: multica-agent <[email protected]> * fix(comment): clear stale task lineage on non-author comment edits (MUL-4857) An admin editing an autopilot Agent's comment previously preserved the comment's original source_task_id. The immediate save is judged on the admin's member identity and correctly fails closed, but the deferred completion-reconcile routes the comment under its original agent author and resolved the delegation authority from the stale source_task_id, resurrecting the autopilot creator's invoke authority once the busy target freed up — an admin (manage rights) could thereby trigger another owner's private agent (invoke rights). Now a content edit re-derives lineage from the edit action: only the agent author editing its own comment re-stamps source_task_id to the current editing task; every other editor (member/admin, or any non-author) clears it, so preview, save, and reconcile all fail closed. Adds a regression covering the admin-edit + busy-target path and syncs the multica-mentioning skill docs. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
cb68669c73 |
feat(composio): gate MCP apps behind feature flag (#4876)
* feat(composio): server-side connect flow + connections REST (Notion MVP) (MUL-3720) (#4608)
* feat(composio): server-side connect flow + connections REST (Notion MVP) (MUL-3720)
Compose the merged server/pkg/composio SDK into a user-facing connection
manager: signed-state connect handshake, local user_composio_connection
mirror, idempotent disconnect, and a per-user MCP session helper (not yet
wired into task dispatch).
- migration 127_user_composio_connection (no FK/cascade, per DB rules)
- sqlc queries: upsert (idempotent on user_id+connected_account_id), list
active, owner-scoped get, mark revoked
- internal/integrations/composio: signed HMAC-SHA256 state, BeginConnect,
CompleteCallback (idempotent upsert), ListConnections, Disconnect
(upstream 404 = idempotent success), CreateMCPSession (no-op when empty,
pins connected_accounts per toolkit), CallbackRedirect
- REST handlers under /api/integrations/composio (user-scoped, 503 when
COMPOSIO_API_KEY unset): connect/init, callback (302), connections list,
delete
- router wiring gated by COMPOSIO_API_KEY; COMPOSIO_AUTH_CONFIGS_JSON maps
toolkit->auth_config (MVP: notion); state secret from COMPOSIO_STATE_SECRET
or derived from JWT_SECRET; callback base from COMPOSIO_CALLBACK_BASE_URL
or MULTICA_PUBLIC_URL
- tests: state (expire/tamper/wrong-secret), service (mapping, callback
idempotency, non-success, disconnect owner/404 idempotency, MCP pin),
handlers (httptest), redact regression for Bearer mcp_ tokens
MVP scope: Notion only; no task-dispatch overlay, sharing, or webhook
event handling (later stages).
Co-authored-by: multica-agent <[email protected]>
* fix(composio): bind callback account to user + idempotent revoked disconnect (MUL-3720)
Address PR 4608 review (CHANGES_REQUESTED):
- callback: verify connected_account_id with Composio before mirroring it.
The signed state only proved user/toolkit/exp, so a valid state paired with
a tampered connected_account_id would be written verbatim. CompleteCallback
now calls ListConnectedAccounts and fails closed (ErrAccountVerification)
unless the account belongs to the state's user (composio_user_id == multica
user id) and was created under the toolkit's auth config. No row is written
on mismatch / unknown account / upstream error.
- disconnect: short-circuit to a no-op when the local row is already revoked,
before touching upstream. Previously a second DELETE re-hit Composio and a
non-404 upstream error surfaced as a 502, breaking the 204-idempotent
contract.
- CreateMCPSession: document the v1 single-active-connection-per-(user,toolkit)
constraint and make duplicate selection deterministic (newest-wins, rows are
connected_at DESC) instead of order-dependent map overwrite. Stage 3 owns the
real single-account-enforcement vs multi-account-shape decision.
Tests: tampered/wrong-auth-config/unknown-account callback rejection, revoked-row
disconnect no-op (asserts upstream not re-hit). composio pkg 85% coverage; all
green.
Co-authored-by: multica-agent <[email protected]>
* feat(composio): list all toolkits + dynamic auth-config resolution (MUL-3720)
Yushen's follow-up to the Notion MVP: surface the full Composio toolkit
catalog, render it in Settings, and drop the static env mapping in favor of
dynamic auth-config discovery.
Config correctness (per Composio docs):
- Remove COMPOSIO_AUTH_CONFIGS_JSON entirely. The toolkit→auth_config mapping
is now resolved at request time from the project's /auth_configs (cached,
5-min TTL), so enabling a toolkit is a dashboard action, not a redeploy.
- Do NOT add COMPOSIO_PROJECT_ID. The project API key (x-api-key) authenticates
to exactly one project; the project is resolved from the key. Only org-level
endpoints use x-org-api-key, which this integration never calls.
Backend:
- SDK: server/pkg/composio/auth_configs.go — ListAuthConfigs (toolkit_slug,
is_composio_managed, show_disabled, limit, cursor).
- service: dynamic resolver (authConfigMap cache; betterAuthConfig prefers a
custom/white-label config over Composio-managed, newest wins); BeginConnect
and CompleteCallback resolve via it; ListToolkits fetches the full catalog
(paginated, capped) annotated with connectable = has an enabled auth config,
connectable-first ordering.
- handler + route: GET /api/integrations/composio/toolkits (user-scoped, 503
when COMPOSIO_API_KEY unset) returning slug/name/logo/category/connectable.
Frontend:
- core: ComposioToolkit/ComposioConnection types, api client methods, and
composio query options (@multica/core/composio).
- views: Settings → Integrations now has a Composio section rendering every
toolkit as a card with search. Connect is gated on `connectable`;
non-connectable toolkits show a muted "not configured" hint instead of a
dead button. Connected toolkits show a badge + Disconnect (with confirm).
- i18n: composio block added to en/zh-Hans/ja/ko settings.
Tests: SDK + service (dynamic resolution, custom-over-managed preference,
connectable flag, resolver-error soft-degrade) and handler toolkits endpoint;
composio pkg 85.7% coverage. go build/vet/gofmt clean; core+views typecheck,
core+views lint, and core tests (691) all green.
Co-authored-by: multica-agent <[email protected]>
* fix(composio): close cross-toolkit callback fail-open by signing auth_config_id into state (MUL-3720)
Re-review blocker: CompleteCallback resolved the toolkit's auth config at
callback time and ignored a resolve error/empty result, while
verifyAccountOwnership skipped the auth-config comparison when the expected
value was empty. A user could then pass another toolkit's connected_account_id
into this toolkit's callback — the owner check passed and it was written under
the wrong toolkit_slug/account binding.
Fix: the auth_config_id is already resolved in BeginConnect (before the state
is signed), so sign it into the state and compare it exactly at callback. No
re-resolve, no fail-open. verifyAccountOwnership now fails closed when the
expected auth config is empty (rejects instead of skipping) and requires an
exact match — closing the cross-toolkit binding gap.
Tests: state round-trips auth_config_id; BeginConnect signs it; callback
rejects wrong/cross-toolkit auth config and an empty (no-mapping) auth config
fails closed. composio pkg 85.2% coverage, all green.
Frontend (non-blocking): the Composio settings tab now surfaces an error when
the connections query fails instead of silently rendering everything as
unconnected.
Co-authored-by: multica-agent <[email protected]>
* fix(composio): hide Settings section entirely when integration unconfigured (MUL-3720)
Decision (option 2, hide-then-merge): don't show a card that leaks the internal
COMPOSIO_API_KEY env-var name to every end user. IntegrationsTab now gates the
whole Composio section (heading + body) on the toolkits query — a 503 means the
key is unset, so the section is withheld instead of rendering the not-configured
card. Admin-only setup guidance is a later, role-gated affordance.
Removed the notConfigured card (and now-unused ApiError import) from
ComposioTab; it only mounts when configured. views typecheck + lint clean.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-agent <[email protected]>
* feat(composio): Stage 2 frontend polish — callback toast, last_used & expired UI, e2e (MUL-3718) (#4688)
* feat(composio): callback toast + refresh, last_used & expired UI, e2e (MUL-3718)
Co-authored-by: multica-agent <[email protected]>
* fix(composio): real callback redirect route + StrictMode-safe toast dedup (MUL-3718 review)
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-agent <[email protected]>
* fix(composio): callback endpoint should not require Multica auth (MUL-3843) (#4709)
* fix(composio): move OAuth callback out of the Auth group (MUL-3843)
Composio 302-redirects the browser to /api/integrations/composio/callback
at the end of the OAuth flow, but PR #4608 mounted it inside the cookie-auth
middleware group. When the session cookie is absent (expired session,
SameSite=Strict / Safari ITP, private window, self-hosted callback subdomain)
the Auth middleware returned a hard 401 and a JSON blob instead of the
settings redirect, breaking the flow.
Identity never came from the cookie anyway: it is carried by the HMAC-signed
state param that CompleteCallback verifies (signature, expiry, replay) and
cross-checked by verifyAccountOwnership; h.Composio == nil still 503s. So the
callback is registered alongside the other public OAuth/webhook routes; the
other four composio endpoints stay session-gated.
Refs MUL-3843, MUL-3715.
Co-authored-by: multica-agent <[email protected]>
* fix(composio): correct stale callback routing comments (MUL-3843)
The package header and ComposioCallback doc comments still described the
callback as sitting under the Auth middleware group. After the route was
moved out (this PR), update both to state it is a public route whose identity
comes from the signed state — addressing review nit from 张大彪.
Refs MUL-3843.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: multica-agent <[email protected]>
* feat(composio): inject MCP overlay into agent runtime at task dispatch (MUL-3721) (#4704)
Stage 3 of the Composio epic. Wires the per-user Composio MCP session into
every agent task so the agent process sees the initiator's connected tools
without any prompt-time plumbing.
Server side
- Migration 128 adds agent_task_queue.runtime_mcp_overlay JSONB plus a
BEFORE-UPDATE trigger that wipes the column on any transition into a
terminal status (completed / failed / cancelled). A trigger is the single
source of truth — future queries that flip status cannot bypass it.
- composio.Service.BuildTaskOverlay(userID) reuses CreateMCPSession and
emits the Claude-style { mcpServers: { composio: { type: http, url,
headers } } } shape the daemon's existing sidecar generators consume.
Returns (nil, nil) on zero active connections so we never burn a
Composio session for a user with nothing to call.
- TaskService grows a Composio ComposioOverlayBuilder seam, wired in
router.go after composiointeg.NewService succeeds. Five enqueue paths
(issue / mention / quick-create / chat / auto-retry) attach the overlay
after CreateAgentTask returns and before the daemon is notified — so
every claim reads a settled row, with no second daemon hop. Best-effort:
a builder failure logs and proceeds with no overlay.
- resolveInitiatorFromTriggerComment derives the initiator user from the
trigger comment when it was authored by a member. Agent-authored
triggers are not treated as initiators (their connected-apps view is
empty by construction).
Daemon side
- handler/daemon.go claim path merges task.runtime_mcp_overlay onto
agent.mcp_config via mergeMCPOverlay before populating
TaskAgentData.McpConfig. Overlay wins on server-name collisions
because it carries the live user-scoped session URL. Errors fall back
to the agent config unchanged — a bad overlay must not surprise-disable
saved MCP tools. The existing execenv sidecar generators (cursor /
codex / openclaw / opencode / hermes-kiro) need no changes: they keep
consuming the merged result through TaskAgentData.McpConfig.
Tests
- 9 merge cases (mcp_overlay_test): both-nil short-circuit, agent-only
pass-through, overlay-only canonicalization, two-side merge, name
collision (overlay wins), top-level key preservation, malformed agent
fallback, malformed overlay fallback, non-object server rejection.
- 4 dispatch cases (composio): zero-connections returns nil without
CreateSession, happy-path emits the right shape with the right user
id, empty-URL defensive branch, SDK error surfacing.
- 4 TaskService helper cases: nil Composio is a no-op (Queries-safe),
invalid initiator does not call the builder, nil overlay skips the
UPDATE, builder error swallowed without panic.
- Migration 128 verified to roll up + down + up cleanly against the test
database.
Out of scope (deferred): assignment-triggered enqueue paths with no
trigger comment get no overlay attached today (no initiator UUID flows
through enqueueIssueTask in that case). Retry paths recompute the overlay
fresh from the parent's initiator_user_id instead of inheriting the bearer
from the parent row, so a stale token can never resurface on a retry.
Co-authored-by: Eve <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(composio): per-agent allowlist + originator-scoped MCP overlay (MUL-3869) (#4736)
* feat(composio): per-agent allowlist + originator-scoped MCP overlay (MUL-3869)
Stage 3.1 of the Composio epic (MUL-3721 parent). PR #4704 wired in the
runtime_mcp_overlay column and a per-task dispatch hook; this change
inverts the default from "all-on" to opt-in and locks the overlay to the
agent owner's own connected apps:
- Agents carry composio_toolkit_allowlist TEXT[]. NULL or [] => no MCP.
Owner-only read/write; non-owner GET/PUT silently redacts/drops the
field (same shape as mcp_config).
- agent_task_queue carries originator_user_id UUID. Set from the
top-of-chain HUMAN at every enqueue path:
* issue/mention comment by member -> author_id
* issue/mention comment by agent -> inherit via comment.source_task_id
-> parent task originator_user_id
* quick-create -> requester_id
* chat -> initiator_user_id
* retry -> SQL-inherited from parent row
* autopilot -> NULL (system-driven)
- BuildTaskOverlay (composio dispatch) now takes (ctx, originatorUserID,
agent) and short-circuits on five gates: invalid originator,
originator != agent.owner_id, empty allowlist, empty intersection of
allowlist ∩ active connections, defensive empty session URL. Composio
CreateSession is called with BOTH `toolkits.slugs` (the intersection)
AND `connected_accounts` (the pinned account ids), narrowing the
tool-router twice.
- The originator-vs-owner gate closes the agent-fanout privacy hole: any
workspace member who can @-mention a public agent used to project the
owner's connected apps into their run. Now the overlay only mounts
when the human at the top of the chain IS the agent owner.
Tests:
- dispatch_test.go covers all 5 gates plus uppercase/whitespace slug
normalisation.
- task_runtime_mcp_overlay_test.go covers the no-op gates of the new
applyRuntimeMCPOverlay signature.
- agent_composio_allowlist_test.go (handler): owner roundtrip
(list/empty/null), workspace-admin silent-drop, owner-only GET
visibility, pure normaliseComposioToolkitAllowlist.
- resolve_originator_test.go (service, DB-backed): member-authored,
agent-authored inherits via comment.source_task_id, invalid id.
Migration 129 up/down/up verified against docker postgres.
Co-authored-by: multica-agent <[email protected]>
* chore(composio): gofmt + regenerate sqlc with v1.31.1 (MUL-3869 review nits)
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Eve <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(composio): accept nested connected account auth config
* feat(views): creator-only MCP tab for per-agent Composio allowlist (MUL-3870) (#4743)
Stage 3.2 frontend on top of the Stage 3.1 backend (MUL-3869,
|
||
|
|
5d79696fb5 | MUL-3794: rewrite comment routing cascade | ||
|
|
906f70a3e2 |
Add comment trigger preview suppression (#3792)
* Add comment trigger preview suppression Co-authored-by: multica-agent <[email protected]> * Use TanStack Query for trigger preview Co-authored-by: multica-agent <[email protected]> * Test note comments skip create triggers Co-authored-by: multica-agent <[email protected]> * feat(issues): redesign comment trigger chips as avatar chips Single agent renders as avatar + presence dot + full sentence; several agents collapse to an overlapping stack + active count, mirroring the header working chip. Per-agent skip moves into a click-opened popover (hover layers stay read-only tooltips); suppression reads as brightness, not a ban glyph. Loading and preview errors render nothing. Also: share one tooltip body across chip and popover rows, invalidate cached previews after a comment lands (the enqueued task changes the dedup answer), move the preview query key into issueKeys, and drop the now-unconsumed status field from useCommentTriggerPreview. Co-Authored-By: Claude Fable 5 <[email protected]> * refactor(server): drop comment trigger wrappers kept only for tests enqueueMentionedAgentTasks and shouldEnqueueSquadLeaderOnComment had no production callers after the compute/enqueue split — the comment path goes through computeCommentAgentTriggers. Tests now exercise the compute functions directly via package-local helpers, so the legacy adapters cannot drift from the real path. Co-Authored-By: Claude Fable 5 <[email protected]> * docs(skills): sync mentioning/squads source maps with shared trigger computation The squads source map still pointed the comment-trigger contract at the pre-refactor call chain (comment.go:940 -> shouldEnqueueSquadLeaderOnComment), and the mentioning skill referenced the deleted wrapper. Re-anchor both to computeCommentAgentTriggers / computeAssignedSquadLeaderCommentTrigger / computeMentionedAgentCommentTriggers with current line numbers. Co-Authored-By: Claude Fable 5 <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> Co-authored-by: Claude Fable 5 <[email protected]> |
||
|
|
dfc159e1aa |
feat: skip agent triggering on /note-prefixed comments (MUL-3115, #3649) (#3885)
* feat(comments): skip agent triggering on /note-prefixed comments A comment whose first token is the reserved /note prefix (case-insensitive) is stored like any other comment but never wakes an agent. The guard sits at the top of triggerTasksForComment, the single chokepoint, so it covers all three trigger paths — assignee, squad leader, and @mentioned agents. Gating only shouldEnqueueOnComment (as originally proposed) would still let "/note @agent ..." through the mention path. Lets members leave human-only tips/notes on agent-assigned issues without burning an agent run. MUL-3115, closes #3649. Co-authored-by: multica-agent <[email protected]> * feat(editor): add /note built-in slash command to comment composer Enable the `/` menu in the issue comment and reply composers in a new "command" mode that lists fixed built-in commands instead of the chat skill picker. Currently one command, /note, which marks a comment as a human-only note that won't trigger the assigned agent. Selecting it inserts the plain-text "/note " prefix (not a rich node), so a menu pick and a hand-typed command are byte-identical and the backend detects either with a simple prefix match. The command menu renders nothing on a non-matching `/` (hideOnEmpty) so typing a date like 6/8 isn't noisy. The chat skill picker is unchanged. MUL-3115. Co-authored-by: multica-agent <[email protected]> * refactor(editor): match /note by label prefix and localize its description Address PR review feedback: - buildBuiltinCommandItems now matches the command label as a prefix only, dropping the description substring match copied from the skill picker. With one command this keeps the menu predictable (/no surfaces note; /deploy or a description word like /agent shows nothing) and avoids Enter selecting note unexpectedly. - The command description is now a localized UI string: added slash_command.commands.note to all four editor locales (en/ja/ko/zh-Hans) and the menu renders it via the typed translator. The /label itself stays literal since it's the typed token the backend matches. MUL-3115. Co-authored-by: multica-agent <[email protected]> * fix(editor): shorten /note command description to avoid truncation The slash menu item is single-line (truncate, w-72), so the longer copy was cut off. Shorten to "won't trigger any agents" across all four locales — also more accurate, since /note skips assignee, squad leader, and @mentioned agents, not just the assigned one. MUL-3115. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
9ccaf18479 |
fix(comment): don't inherit parent @mentions from agent-authored roots (#1833)
* fix(comment): don't inherit parent @mentions when parent author is an agent When an agent posts a comment that @mentions another agent (typically a one-shot delegation, e.g. a PR-completion comment that asks a reviewer agent to review), member follow-up replies in the same thread were auto-inheriting that mention and re-triggering the reviewer on every plain question. Same root cause: the inheritance branch only required the reply to have no mentions, not that the parent was member-authored. Tighten the guard: only inherit when the parent (thread root) is authored by a member. Member-rooted threads still inherit so a member who started by @mentioning an agent can keep replying without re-typing. Agent-authored roots are treated as one-shot — explicit @mentions in later comments still trigger normally. Extracted the decision into shouldInheritParentMentions for direct unit testing, and added an end-to-end regression (TestMemberReplyToAgentRootDoesNotInheritParentMentions) that reproduces MUL-1535: J posts a PR completion @mentioning Reviewer; a member's plain follow-up must not re-enqueue Reviewer. * chore(comment): gofmt trigger_test.go |
||
|
|
bf71802451 |
fix(server): trigger agent on reply in thread where agent already participated (#981)
When a member replies in a member-started thread without @mentioning the assigned agent, the on_comment trigger was suppressed — even if the agent had already replied in that thread. This meant the common flow of "member posts → agent replies → member follows up" would not re-trigger the agent on the follow-up. Add HasAgentRepliedInThread SQL query and check it in isReplyToMemberThread so that agent participation in a thread is treated as an ongoing conversation. |
||
|
|
7c79611309 |
refactor: remove agent triggers config field (#469)
* 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) <[email protected]> * 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) <[email protected]> * 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) <[email protected]> --------- Co-authored-by: Devv <[email protected]> Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]> |
||
|
|
beeb8bc107 |
Merge pull request #361 from JimmyPang02/fix/issue-mention-on-comment
fix: issue mentions should not suppress on_comment trigger |
||
|
|
fc6405e4be |
fix(trigger): allow on_comment when thread root @mentions assignee agent (#382)
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. |
||
|
|
6799458807 | fix: issue mentions should not suppress on_comment trigger | ||
|
|
02c9d9f0b0 |
fix(server): @all mentions should not trigger agent execution
@all is a broadcast to all workspace members — it should not trigger the assignee agent's on_comment. Previously @all was treated as "includes everyone" and allowed the trigger. Changes: - commentMentionsOthersButNotAssignee now checks HasMentionAll() early and returns true (suppress) when @all is present - Fix authRequestWithAgent test helper that was making a duplicate HTTP request (one as member, one as agent) Tests: 5 new @all unit test cases, 2 new @all integration test cases. |
||
|
|
4f0c2afcb7 |
test(server): add unit tests for comment trigger logic
Tests cover all on_comment trigger decision scenarios: - commentMentionsOthersButNotAssignee: 6 cases - isReplyToMemberThread: 7 cases - Combined trigger decision: 9 cases matching the full decision table - agentHasTriggerEnabled: 7 cases (nil, empty, explicit, backwards compat) - defaultAgentTriggers: validates all 3 triggers present and enabled |