feat(agents): align create name defaults

This commit is contained in:
2026-06-06 02:26:42 +08:00
parent 3c30596bad
commit dd2bdee470
4 changed files with 47 additions and 6 deletions
+6 -4
View File
@@ -16,10 +16,10 @@ Build GoChat as a Go backend that can directly reuse the frontend from `referenc
## Current Baseline
- Current tracking checkpoint: 2026-06-06 after `0fb4e6c feat(profile): align account permissions`, with this implementation checkpoint prepared as `feat(attachments): align list pagination`.
- Latest implementation checkpoint: this checkpoint, prepared as `feat(attachments): align list pagination`.
- Current tracking checkpoint: 2026-06-06 after `3c30596 feat(attachments): align list pagination`, with this implementation checkpoint prepared as `feat(agents): align create name defaults`.
- Latest implementation checkpoint: this checkpoint, prepared as `feat(agents): align create name defaults`.
- Latest documentation-only checkpoint: `8b378c7 docs: refresh parity tracker baseline`; this document is now the active follow-up plan and supersedes `.hermes/plans/*`.
- Worktree status at this implementation checkpoint: Account conversation/contact attachment list pagination now matches Chatwoot's fixed 100-row page size for `ConversationsController#attachments` and `Contacts::AttachmentsController#index`; reused frontend `per_page` query values no longer shrink shared attachment payloads below the reference page size. Profile account permission drift is closed for reused route guards and dashboard permission helpers: profile account entries now return Chatwoot `AccountUser#permissions` for ordinary roles (`["administrator"]` or `["agent"]`) and continue to return enterprise custom-role permissions plus `custom_role` when `custom_role_id` is present. The preceding platform account-user membership, agents list ordering, update-route/`auto_offline`, agents bulk invite, B10.3 CustomRole frontend update, and Phase 6 contacts pagination/active payload drifts are closed. Conversation transcript and custom-attribute response drifts are already closed. The fresh placeholder audit found remaining `chatwootParityStub` routes only in webhook handler-not-injected fallbacks, not in the reused dashboard account/contact/conversation/message/inbox critical path. P3.6 custom filters/custom attribute definitions and label CRUD are already in Review. B11.1a-B11.3e Captain/Copilot slices remain in Review; B12.1/B12.2/B12.3 smoke harnesses remain in Review; P5 durable job work through P5.13b is in Review. Next active implementation slice is the next named Phase 2/3 or Phase 6 drift from fresh reference/smoke evidence.
- Worktree status at this implementation checkpoint: Agent creation now matches Chatwoot `AgentBuilder` name fallback: blank or omitted `agent[name]` no longer fails local validation and new users receive the email local-part as `name`. Account conversation/contact attachment list pagination already matches Chatwoot's fixed 100-row page size for `ConversationsController#attachments` and `Contacts::AttachmentsController#index`; reused frontend `per_page` query values no longer shrink shared attachment payloads below the reference page size. Profile account permission drift is closed for reused route guards and dashboard permission helpers: profile account entries now return Chatwoot `AccountUser#permissions` for ordinary roles (`["administrator"]` or `["agent"]`) and continue to return enterprise custom-role permissions plus `custom_role` when `custom_role_id` is present. The preceding platform account-user membership, agents list ordering, update-route/`auto_offline`, agents bulk invite, B10.3 CustomRole frontend update, and Phase 6 contacts pagination/active payload drifts are closed. Conversation transcript and custom-attribute response drifts are already closed. The fresh placeholder audit found remaining `chatwootParityStub` routes only in webhook handler-not-injected fallbacks, not in the reused dashboard account/contact/conversation/message/inbox critical path. P3.6 custom filters/custom attribute definitions and label CRUD are already in Review. B11.1a-B11.3e Captain/Copilot slices remain in Review; B12.1/B12.2/B12.3 smoke harnesses remain in Review; P5 durable job work through P5.13b is in Review. Next active implementation slice is the next named Phase 2/3 or Phase 6 drift from fresh reference/smoke evidence.
- `go test ./...` passes.
- Route dump succeeds with `TOTAL: 840` after adding the Chatwoot platform account-user collection destroy route.
- Route parity artifacts now exist under `docs/parity/` and are generated by `cmd/route_parity`.
@@ -87,7 +87,7 @@ This table is the shortest authoritative handoff view. If an older lower section
| Priority | Workstream | Current state | Next checkpoint | Commit close rule |
| --- | --- | --- | --- | --- |
| 1 | Phase 2/3 drift | Tracked route parity is 0 missing for the current critical set; notification list/action serializers, Devise password reset/confirmation payloads, CRM shared attachment payloads plus fixed 100-row attachment pagination, account/settings payloads, assignable-agent payloads, label CRUD payloads, custom filters, and custom attribute definitions now match the inspected Chatwoot frontend contract. | Continue Phase 6 placeholder audit, then open the next named serializer/controller drift slice from reference/smoke evidence. | Regenerate parity artifacts when routes change and add endpoint-family fixture tests. |
| 1 | Phase 2/3 drift | Tracked route parity is 0 missing for the current critical set; notification list/action serializers, Devise password reset/confirmation payloads, CRM shared attachment payloads plus fixed 100-row attachment pagination, account/settings payloads, assignable-agent payloads, agent create defaults, label CRUD payloads, custom filters, and custom attribute definitions now match the inspected Chatwoot frontend contract. | Continue Phase 6 placeholder audit, then open the next named serializer/controller drift slice from reference/smoke evidence. | Regenerate parity artifacts when routes change and add endpoint-family fixture tests. |
| 2 | Phase 6 placeholder audit | Widget/public/webhook critical placeholders are burned down; inbox WhatsApp health/register-webhook and sync-template drift are closed; fresh `rg` audit shows only webhook nil-handler fallbacks still call `chatwootParityStub`; dashboard conversation transcript response drift is closed. | Continue targeted account/contact/conversation/message/inbox drift from reference/smoke evidence. | `rg` placeholder audit is recorded and no reused-frontend blocker is ownerless. |
| 3 | B12 optional live smoke | API/browser/enterprise smoke commands are checked in; live runs need PostgreSQL, Redis, Meilisearch, Vite, and Chrome. | Run full live smoke when environment is available and map failures to the board. | `docs/parity/frontend_smoke_report.md` records pass/fail and linked owners. |
| 4 | B9.3 delayed automation actions | Current reference exposes no delayed automation action params; scheduled-item work is already P5.12; `send_email_to_team` is durable and `add_sla` mutates conversation/applied SLA state. | Keep automation drift closed if future reference/smoke exposes delayed params or unsupported action shapes. | Automation worker/action fixtures verify queued team email replay, retry visibility through worker jobs, and SLA action idempotency. |
@@ -143,6 +143,7 @@ This ledger records the committed parity checkpoints that future slices should b
| Commit | Scope | Verification summary | Follow-up state |
| --- | --- | --- | --- |
| `feat(agents): align create name defaults` | Advances P3.2 agents/settings parity by matching Chatwoot `AgentsController#create` and `AgentBuilder`. Agent creation now accepts blank or omitted `agent[name]`; for newly created users, the name falls back to the email local-part exactly like `email.split('@').first`, while existing-user invitation behavior remains unchanged. | `go test ./internal/handler/api/v1 -run 'TestAgentHandlerSuite/TestCreateAgent' -count=1`; `go test ./internal/service ./internal/repository -run Agent -count=1`; `go test ./internal/handler/api/v1 ./internal/service ./internal/repository -count=1`; `go test ./...`; `git diff --check`. No route changes; route dump remains `TOTAL: 840`. | Continue P3.2 agents/account-user drift or the next Phase 6 reference/smoke finding. |
| `feat(attachments): align list pagination` | Advances Phase 6 dashboard/CRM attachment drift by matching Chatwoot `ConversationsController#attachments`, `Contacts::AttachmentsController#index`, and the shared attachment Jbuilder payloads. Conversation and contact attachment lists now page with the reference fixed 100-row limit from `ATTACHMENT_RESULTS_PER_PAGE`/`RESULTS_PER_PAGE` and ignore reused-frontend `per_page` query values, while retaining newest-first attachment ordering and `{ meta: { total_count }, payload }` responses. | `go test ./internal/handler/api/v1 -run 'Test(ConversationHandlerTestSuite|ContactHandlerCRUDTestSuite)/TestListAttachments' -count=1`; `go test ./internal/handler/api/v1 ./internal/service ./internal/repository -count=1`; `go test ./...`; `git diff --check`. No route changes; route dump remains `TOTAL: 840`. | Continue the next named Phase 2/3 or Phase 6 drift from reference/smoke evidence. |
| `feat(profile): align account permissions` | Advances P3.1/P3.2 profile and permission parity by matching Chatwoot `api/v1/models/_user.json.jbuilder`, `AccountUser#permissions`, and the enterprise custom-role prepend used by dashboard route guards. Profile account entries now expose ordinary role permissions as `["administrator"]` or `["agent"]`, and custom-role memberships expose the role's permission keys plus `custom_role` while retaining nested `custom_role` metadata. | `go test ./internal/handler/api/v1 -run ProfileHandler -count=1`; `go test ./internal/handler/api/v1 ./internal/service ./internal/repository -count=1`; `go test ./...`; `git diff --check`. No route changes; route dump remains `TOTAL: 840`. | Continue P3.2 permissions/invitations drift or the next Phase 6 reference/smoke finding. |
| `feat(platform): align account user membership` | Advances P3.2 platform account-user parity by matching `Platform::Api::V1::AccountUsersController`. Platform account-user index/create now return raw AccountUser JSON instead of the local `{ success, data }` envelope, create uses Chatwoot `find_or_initialize_by(user_id).update!` semantics so duplicate membership requests update role instead of failing, and the Chatwoot collection route `DELETE /platform/api/v1/accounts/:account_id/account_users/destroy?user_id=...` is registered while the legacy `/:user_id` alias remains. | `go test ./internal/handler/api/v1 -run PlatformAccountUser -count=1`; `go run ./cmd/dump_routes > docs/parity/gochat_routes.txt`; `go run ./cmd/route_parity`; `go test ./internal/handler/api/v1 ./internal/repository ./internal/router -count=1`; `go test ./...`; `git diff --check`. Route dump is `TOTAL: 840`; tracked route parity remains `270 exact, 7 parameter-compatible, 0 missing`. | Continue P3.2 permissions/invitations drift or the next Phase 6 reference/smoke finding. |
@@ -2078,6 +2079,7 @@ Verification milestone gates:
## Progress Log
- 2026-06-06: P3.2 agent create-default checkpoint prepared as `feat(agents): align create name defaults`; audited Chatwoot `AgentsController#create`, `AgentBuilder`, `AccountUser`, and the reused dashboard agents API. Agent creation now accepts blank/omitted `agent[name]`, derives new-user names from the email local-part like `email.split('@').first`, and keeps existing agent create role/availability/auto-offline behavior intact. Focused agent create tests, service/repository agent tests, combined handler/service/repository tests, full `go test ./...`, and `git diff --check` passed; continue P3.2 agents/account-user drift or the next Phase 6 reference/smoke finding.
- 2026-06-06: Phase 6 attachment pagination checkpoint prepared as `feat(attachments): align list pagination`; audited Chatwoot `ConversationsController#attachments`, `Contacts::AttachmentsController#index`, and the shared attachment Jbuilder partials. Conversation and contact shared attachment lists now use Chatwoot's fixed 100-row page size and ignore `per_page` query values, preserving `{ meta: { total_count }, payload }`, newest-first ordering, sender serialization, and contact attachment `conversation_id`. Focused attachment suite tests, combined handler/service/repository tests, full `go test ./...`, and `git diff --check` passed; continue the next Phase 2/3 or Phase 6 drift from reference/smoke evidence.
- 2026-06-06: P3.1/P3.2 profile permissions checkpoint prepared as `feat(profile): align account permissions`; audited Chatwoot `_user.json.jbuilder`, `AccountUser#permissions`, enterprise `Enterprise::AccountUser`, and dashboard `permissionsHelper.js`. Profile account entries now expose `["administrator"]`/`["agent"]` for ordinary roles and custom-role permission keys plus `custom_role` for custom-role memberships, preserving nested custom-role metadata for the reused frontend. Focused profile tests, combined handler/service/repository tests, full `go test ./...`, and `git diff --check` passed; continue P3.2 permissions/invitations drift next.
- 2026-06-06: P3.2 platform account-user checkpoint prepared as `feat(platform): align account user membership`; audited Chatwoot `Platform::Api::V1::AccountUsersController` and platform routes. Platform account-user index/create now return raw AccountUser JSON, create updates existing memberships instead of failing duplicates, the user-permissible extra gate was removed to match the account-scoped reference controller, and collection destroy is available as `DELETE /account_users/destroy` with `user_id`. Focused platform account-user tests, route generation/parity, combined handler/repository/router tests, full `go test ./...`, and `git diff --check` passed; continue P3.2 permissions/invitations drift next.
@@ -158,6 +158,27 @@ func (s *AgentHandlerTestSuite) TestCreateAgent() {
assert.Contains(s.T(), data, "confirmed")
}
func (s *AgentHandlerTestSuite) TestCreateAgentDefaultsBlankNameFromEmail() {
req := map[string]interface{}{
"agent": map[string]interface{}{
"email": "fallback-name@test.com",
"name": "",
},
}
w, c := s.makeRequest("POST", "/api/v1/accounts/1/agents", req, s.account.ID, s.user.ID)
s.handler.Create(c)
assert.Equal(s.T(), http.StatusOK, w.Code, w.Body.String())
var data map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &data)
assert.Equal(s.T(), "fallback-name@test.com", data["email"])
assert.Equal(s.T(), "fallback-name", data["name"])
var user model.User
s.Require().NoError(s.db.Where("email = ?", "fallback-name@test.com").First(&user).Error)
assert.Equal(s.T(), "fallback-name", user.Name)
}
func (s *AgentHandlerTestSuite) TestCreateAgentDuplicate() {
// Create first agent
req := service.CreateAgentRequest{
+6
View File
@@ -142,6 +142,12 @@ func (r *AgentRepo) CreateAgent(ctx context.Context, accountID uint, inviterID u
err := r.db.WithContext(ctx).Where("email = ?", email).First(&user).Error
if err == gorm.ErrRecordNotFound {
// Create new user
if name == "" {
name = email
if atIdx := indexOfAt(email); atIdx > 0 {
name = email[:atIdx]
}
}
user = model.User{
Name: name,
Email: email,
+14 -2
View File
@@ -39,7 +39,7 @@ func (s *AgentService) DB() *gorm.DB {
// Reference: Chatwoot agents_controller.rb#create → new_agent_params (email, name, role, availability, auto_offline)
type CreateAgentRequest struct {
Email string `json:"email" validate:"required,email"`
Name string `json:"name" validate:"required,min=1"`
Name string `json:"name" validate:"omitempty,min=1"`
Role string `json:"role" validate:"omitempty,oneof=agent administrator"`
Availability string `json:"availability" validate:"omitempty,oneof=online offline busy"`
AutoOffline bool `json:"auto_offline"`
@@ -108,12 +108,16 @@ func (s *AgentService) Create(ctx context.Context, accountID uint, inviterID uin
if availability == "" {
availability = "offline"
}
name := strings.TrimSpace(req.Name)
if name == "" {
name = agentNameFromEmail(req.Email)
}
customRoleID := uint(0)
if req.CustomRoleID != nil {
customRoleID = *req.CustomRoleID
}
detail, err := s.agentRepo.CreateAgent(ctx, accountID, inviterID, req.Name, req.Email, role, availability, req.AutoOffline, customRoleID)
detail, err := s.agentRepo.CreateAgent(ctx, accountID, inviterID, name, req.Email, role, availability, req.AutoOffline, customRoleID)
if err != nil {
if errors.Is(err, repository.ErrAlreadyMember) {
return nil, repository.ErrAlreadyMember
@@ -171,6 +175,14 @@ func isValidBulkAgentEmail(email string) bool {
return err == nil && addr.Address == email
}
func agentNameFromEmail(email string) string {
email = strings.TrimSpace(email)
if at := strings.Index(email, "@"); at > 0 {
return email[:at]
}
return email
}
// AvailableAgentCount returns the number of additional agents that can be added to an account.
// Reference: Chatwoot agents_controller.rb#available_agent_count — usage_limits[:agents] - agents.count
// Returns -1 if no limit is set (0 = unlimited in our convention).