Why: the user ruled out self-registration (2026-09-30): accounts are created only by admins who set an initial password; first login (and first login after any admin reset) must force a password change. Evidence: - migration v10 auth_accounts (users password/must_change/is_admin + sessions table; upgrade test walks v1<->v10 both directions). - argon2id hash/verify with PHC encoding; store tests cover hash roundtrip, duplicate-owner conflict, must-change lifecycle, session keep/except semantics. - e2e TestAdminAccountFlow: static login page reachable without auth, wrong password 401, login sets HttpOnly cookie + mustChangePassword, admin creates user (409 on duplicate), non-admin create 403, forced change clears flag and old password dies, admin reset revokes sessions and re-arms forced change, API-key channel untouched, 5-fail backoff 429, logout kills session. Full go test ./... green. Impact: middleware now accepts Bearer key OR session cookie and exempts static + /api/auth/login (also fixes the latent case where the login page itself could 401). Embedded no-auth mode unchanged. Refs: proposal 2026-09-30-ui-redesign-and-auth.md section 5 (admin-issued variant, user decision 2026-09-30)
81 lines
2.7 KiB
Go
81 lines
2.7 KiB
Go
// Package profiles 管理智能体画像(4.7):数据化、可导入导出;内置默认画像(T0 基线)。
|
||
package profiles
|
||
|
||
import (
|
||
"reflect"
|
||
|
||
"agent-system-backend/internal/store"
|
||
core "agent-system-core"
|
||
)
|
||
|
||
// OwnerDefault 与 store 保持一致的默认租户(内置画像共享,owner=”)。
|
||
const OwnerDefault = store.OwnerDefault
|
||
|
||
const DefaultProfileID = "default"
|
||
|
||
// defaultSystemPrompt 默认画像不再携带常驻提示词:基座(身份/行为/谨慎/工具/风格)
|
||
// 与环境段由提示词库组装(prompts 库 base.*/env.*);文档任务输出要求已技能化
|
||
// (task.doc-minutes 等,按触发注入)。SystemPrompt 留空 = 完全走库。
|
||
const defaultSystemPrompt = ""
|
||
|
||
func DefaultProfile() core.AgentProfile {
|
||
maxTools := 40
|
||
maxTurns := 16
|
||
return core.AgentProfile{
|
||
ID: DefaultProfileID,
|
||
Name: "通用助手",
|
||
Description: "内置默认画像:全部工具可用,HTTP 工具执行前确认(T0 基线)。",
|
||
Builtin: true,
|
||
Source: "builtin",
|
||
SystemPrompt: defaultSystemPrompt,
|
||
Model: core.ModelRoute{},
|
||
Tools: core.ToolPolicy{Allow: []string{"*"}, Confirm: []string{"http."}},
|
||
Context: core.ContextPolicy{MaxHistoryMessages: 40, MaxToolResultChars: 20000},
|
||
HITL: core.HITLPolicy{AskOnAmbiguity: true},
|
||
Budget: core.RunBudget{MaxToolCalls: &maxTools, MaxTurns: &maxTurns},
|
||
}
|
||
}
|
||
|
||
type Service struct{ st *store.Store }
|
||
|
||
func New(st *store.Store) *Service { return &Service{st: st} }
|
||
|
||
// EnsureDefault 幂等写入内置画像;内置画像只读(Save 拒绝编辑),库中与内置定义
|
||
// 有差异的行只可能来自更早版本的内置定义,因此差异时升级为当前内置定义。
|
||
func (s *Service) EnsureDefault() error {
|
||
if cur, err := s.st.GetProfile(OwnerDefault, DefaultProfileID); err == nil {
|
||
if reflect.DeepEqual(cur, DefaultProfile()) {
|
||
return nil
|
||
}
|
||
}
|
||
return s.st.SaveProfile(DefaultProfile())
|
||
}
|
||
|
||
func (s *Service) List(owner string) ([]core.AgentProfile, error) { return s.st.ListProfiles(owner) }
|
||
|
||
func (s *Service) Get(owner, id string) (core.AgentProfile, error) {
|
||
p, err := s.st.GetProfile(owner, id)
|
||
if err == nil {
|
||
return p, nil
|
||
}
|
||
// 空或未知画像回落默认(T0 行为基线)。
|
||
return DefaultProfile(), nil
|
||
}
|
||
|
||
func (s *Service) Save(owner string, p core.AgentProfile) error {
|
||
if p.ID == DefaultProfileID {
|
||
return errReadOnly("profiles: builtin profile is read-only; duplicate and modify")
|
||
}
|
||
if p.Tools.Allow == nil {
|
||
p.Tools.Allow = []string{"*"}
|
||
}
|
||
p.Owner = owner
|
||
return s.st.SaveProfile(p)
|
||
}
|
||
|
||
func (s *Service) Delete(owner, id string) error { return s.st.DeleteProfile(owner, id) }
|
||
|
||
type errReadOnly string
|
||
|
||
func (e errReadOnly) Error() string { return string(e) }
|