mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-09 01:46:46 +08:00
* fix(mobile): gate the chat agent picker on the real invoke rule (MUL-6380) apps/mobile/lib/can-assign-agent.ts was a hand-copied mirror of the pre-MUL-3963 permission model and had drifted from the server in both directions: - it granted workspace owner/admin a bypass on private agents, so an admin could pick a teammate's personal agent in the chat picker and only learn it was refused when the send hit canInvokeAgent (403); - it judged on the derived `visibility` field, which collapses "public_to scoped to specific people" into "private" — so a member the owner had explicitly granted access to never saw that agent on mobile at all. Delete the mirror and call `canAssignAgentToIssue` from @multica/core/permissions directly, the same rule the issue mention bar on this app already uses (mention-suggestion-bar.tsx). The root CLAUDE.md sharing rule allows mobile to import pure functions from @multica/core, so the mirror bought nothing but a second copy to drift. No new tests: the access matrix stays canonical in packages/core/permissions/rules.test.ts, which already covers the admin, owner, plain-member and member-target cases. Co-authored-by: multica-agent <[email protected]> * fix(agents): stop telling users workspace admins can run a personal agent (MUL-6380) Since MUL-3963 the invoke gate is owner-only for a private agent — admins keep management and view access, not the ability to trigger a run. Several strings still describe the old model, and they sit next to controls that enforce the new one: the "Personal" badge tooltip on the agent detail page, overview summary and hover card says "only you and workspace admins can use this agent", while the assignee picker beside it is disabled and reads "only the owner can assign work". - packages/core/agents/visibility-label.ts: fix VISIBILITY_DESCRIPTION and VISIBILITY_TOOLTIP (shown in the create-agent dialog, the visibility picker and the agents-list lock tooltip). The description covers a CHOICE, which submits permission_mode "private" — owner-only, so "Only you can assign". The tooltip covers an EXISTING agent, where `visibility: "private"` is the lossy projection that also covers "shared with specific people" — so it names the owner's grants instead of promising either extreme. The doc comments asserted the opposite contract and cited a long-stale line ref. - packages/views/locales/{en,zh-Hans,ja,ko}/agents.json: same fix for the VisibilityBadge tooltip, which is also its aria-label. Co-authored-by: multica-agent <[email protected]> * fix(issues): stop naming the target's permission mode when an assign is denied (MUL-6380) validateAssigneePair answered a refused assignment with "cannot assign to private agent" / "cannot assign to squad with private leader". Both are wrong twice over: - they disclose the target's permission mode to a caller who was just told they may not use it, which is exactly what ReasonInvocationNotAllowed (dispatch/reason.go) deliberately avoids — it does not distinguish "not permitted" from "does not exist"; - "private agent" is simply inaccurate for a `public_to` agent scoped to specific people, which is refused through the same branch. Report the missing permission instead of the target's configuration. Co-authored-by: multica-agent <[email protected]> * fix(issues): correct the authorization contract stated at the assignment gate (MUL-6380) Review found the drift this PR set out to fix still present in the comments and test docs closest to the gate itself: - validateAssigneePair's doc comment claimed assignment runs canAccessPrivateAgent and that agent callers bypass the gate. It runs canInvokeAgent, and an agent caller is judged by the top-of-chain human originator like anyone else — the code twenty lines below has said so since MUL-3963. - TestShouldEnqueueOnComment_PrivateAgentGate's doc listed "allow workspace owners/admins" and "allow agent-to-agent traffic regardless of visibility"; its own cases assert both are DENIED. - TestCreateIssue_SquadPrivateLeader_OwnerAllowed's doc said a workspace owner can assign; the body uses the leader agent's owner, and an inline comment already corrected the mismatch. - The comment added earlier in this PR overclaimed: it read as if the 403 were enumeration-safe, but the not-in-workspace branch still answers 400. Narrowed to the guarantee actually made — the reason no longer names the permission mode. Also pins the denial BODY, which no test covered: status-only assertions let the reason sentence drift back into describing the target. New cases cover the workspace owner, a plain member, an agent actor with no originator, a `public_to` agent scoped to someone else (the case the old wording called "private", which was simply false), and a private-leader squad. Co-authored-by: multica-agent <[email protected]> * fix(chat): make a session read-only when its agent can no longer be run (MUL-6380) Filtering the agent PICKER was only half the path. A chat session outlives the permission that created it: the agent can be flipped to personal, change owner, or drop the member from its allow-list. The server already treats that as a defined state — the transcript stays readable through the view gate while every send is refused by the invoke gate (MUL-4525, admission_security_mul4525_test.go) — but the clients kept resolving the open session's agent from the full list and never re-checked whether it could still be run. The composer looked live, and the user found out from a 403. Judge the SESSION's agent, not just the picker list, on every surface: - packages/views: `isAgentAccessRevoked` in use-chat-controller.ts and chat-window.tsx, wired into the composer disable, upload affordance, quick actions, queued send-now, and a new AgentAccessRevokedBanner. handleSend keeps a belt-and-braces guard so it never spends a roundtrip on a certain refusal. - apps/mobile: same derivation in the chat tab, plus the failure path — a thrown send only restored the draft, so the 403 read as a silent no-op. It now names the cause, via a mobile-local `dispatchReasonCode` (core's narrows on core's ApiError class, which mobile never throws). Two reason-precedence fixes fall out of this. "No agents in this workspace" was shown when the workspace's ONLY agent was the revoked one — an agent IS bound, it just cannot be run — so revoked access now wins in the placeholder, the banner and mobile's disabled reason. And the queue's Steer button said "Steer is available after the current reply starts", telling the user to wait for something waiting cannot fix; it now states the permission reason. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]>