Skip to content

refactor(work-items): split human assignee from execution target - #874

Draft
Harry19081 wants to merge 1 commit into
developfrom
refactor/work-item-human-assignee
Draft

refactor(work-items): split human assignee from execution target#874
Harry19081 wants to merge 1 commit into
developfrom
refactor/work-item-human-assignee

Conversation

@Harry19081

Copy link
Copy Markdown
Member

Summary

The Work Item assignee field meant two different things at once — a roster person, or the agent definition / agent organization that would execute the item. This makes assignment mean a human, and leaves execution targeting where it already belonged: orchestratorConfig.agent_definition_id / org_id.

Problem

AssigneePropertyField offered members, agents, and agent orgs in one dropdown, tagging the choice with assigneeType: "human" | "agent" | "org". Both creation paths then copied whatever the draft carried straight into the persisted row:

assignee: draft.assigneeId,
assigneeType: draft.assigneeType,

So picking an executor wrote an identity claim onto the row, and auto-execute gated on assigneeType === "agent" | "org" — meaning you could not run an item on an agent and have it assigned to the person who owns it. The two concepts also shared vocabulary (resolveAiWorkItemAssignee, defaultAiWorkItemAssignee), so the overload was invisible at every call site.

Solution

  • Source-level invariant. New humanAssignee.tsresolveHumanAssigneeWrite accepts human (and the legacy member spelling, canonicalized to human) and returns {} for agent/org identities. Both producing write paths, createWorkItemFromDraft and useAiWorkItemCreator, spread its result instead of copying draft fields, so an execution identity can no longer reach the assignee column from either direction.
  • Picker contract. AssigneePropertyField takes Person[] only. The agent/org option groups and their agentsGroup / orgsGroup strings are gone, along with the availableAgents / availableOrgs / allAgentList prop chain threaded through PeopleSection, WorkItemProperties, WorkItemDetailBody, and InlineCreateWorkItemFields. handleAssigneeChange loses its assigneeType parameter — buildHumanAssigneeUpdate now owns the type.
  • Execution target. useAiWorkItemCreator resolves an executionTarget from orchestrator config first, then creator selection, then the OS Agent fallback — renamed throughout (defaultAiWorkItemExecutionTarget, defaultAiExecutionTarget) so the two concepts stop sharing names. Auto-execute gates on a configured target rather than on assigneeType, and the two toasts were renamed to match (chooseExecutionAgentAi, autoExecuteRequiresExecutionAgent, across all 13 locales).

Rows that already carry an agent or org assignee still render — the icon branch for non-human identities is deliberately kept. They are simply no longer offered as new options. Historical cleanup is a separate, confirmed remediation task.

Validation / Test plan

  • pnpm typecheck clean across the repo
  • ESLint clean on all changed source files
  • New regression coverage at the producing boundaries, not just the render path:
    • humanAssignee.test.ts — canonicalizes human/member, rejects agent/org/agent_org/undefined
    • createWorkItemFromDraft.test.ts — an agent draft produces a request with no assignee/assigneeType key at all
    • useWorkItemPropertyHandlers.test.tsbuildHumanAssigneeUpdate writes and clears identity + type together
    • AssigneePropertyField.test.ts — the picker lists roster members and never agent or org identities, even when legacy props are still passed
  • vitest run src/modules/ProjectManager/WorkItems/ src/engines/ChatPanel/ — 206 files / 1363 tests pass
  • frontend-ui-audit report at docs/frontend-ui-audit-2026-08-21/AssigneePropertyField.md — 5 fix / 2 keep-with-reason / 1 abstract, 0 cross-file sweep candidates

Potential risks

  • Existing agent/org-assigned rows. Nothing rewrites them; they render as before but their assignee can only be replaced with a human or cleared. If any list filter or grouping keys off assigneeType === "agent", it will now see a shrinking set over time. Worth a look at the kanban swimlane and Work Item filter surfaces before merge.
  • Auto-execute gate moved. It now reads orchestratorConfig. An item whose agent was only ever expressed through assigneeType will read as "no execution target" and block auto-execute until an agent is picked — correct, but a visible behavior change for anyone mid-flow.
  • Back-end assigneeType still accepts the old values; this PR only closes the frontend write paths. A Rust-side validation follow-up would make the invariant total.

The assignee field had two meanings at once: a roster person, or an agent
definition / agent organization that would execute the item. The picker
offered all three in one list, and both creation paths copied whichever the
draft carried straight into the persisted `assignee` / `assigneeType`
columns — so an execution choice became an identity claim on the row.

Assignment now means a human. Execution targeting lives where it already
belonged, in `orchestratorConfig.agent_definition_id` / `org_id`.

- New `humanAssignee.ts` boundary: manual and AI creation both route draft
  assignment through `resolveHumanAssigneeWrite`, which canonicalizes the
  legacy `member` spelling to `human` and drops agent/org identities instead
  of persisting them.
- `AssigneePropertyField` takes `Person[]` only; the agent and organization
  groups (and their `agentsGroup` / `orgsGroup` strings) are gone, along with
  the `availableAgents` / `availableOrgs` / `allAgentList` prop chain through
  `PeopleSection`, `WorkItemProperties`, `WorkItemDetailBody`, and
  `InlineCreateWorkItemFields`.
- `useAiWorkItemCreator` resolves an execution target from orchestrator
  config first, then creator selection, then the OS Agent fallback —
  renamed throughout to `executionTarget` so the two concepts stop sharing
  vocabulary. Auto-execute now gates on a configured target rather than on
  `assigneeType`.

Existing rows that already carry an agent or org assignee still render; they
are simply no longer offered as new assignment options. Historical cleanup is
a separate, confirmed remediation task.

Pre-commit hook ran. Total eslint: 4, total circular: 0
@Harry19081
Harry19081 marked this pull request as draft August 22, 2026 15:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant