diff --git a/backend/app/api/hr.py b/backend/app/api/hr.py index ca03c6b7..84cdda03 100644 --- a/backend/app/api/hr.py +++ b/backend/app/api/hr.py @@ -3,10 +3,10 @@ from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException from sqlmodel import Session, select -from app.api.utils import log_activity +from app.api.utils import log_activity, get_actor_employee_id from app.db.session import get_session -from app.models.hr import EmploymentAction, HeadcountRequest -from app.schemas.hr import EmploymentActionCreate, HeadcountRequestCreate, HeadcountRequestUpdate +from app.models.hr import EmploymentAction, HeadcountRequest, AgentOnboarding +from app.schemas.hr import EmploymentActionCreate, HeadcountRequestCreate, HeadcountRequestUpdate, AgentOnboardingCreate, AgentOnboardingUpdate router = APIRouter(prefix="/hr", tags=["hr"]) @@ -17,18 +17,18 @@ def list_headcount_requests(session: Session = Depends(get_session)): @router.post("/headcount", response_model=HeadcountRequest) -def create_headcount_request(payload: HeadcountRequestCreate, session: Session = Depends(get_session)): +def create_headcount_request(payload: HeadcountRequestCreate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): req = HeadcountRequest(**payload.model_dump()) session.add(req) session.commit() session.refresh(req) - log_activity(session, actor_employee_id=req.requested_by_manager_id, entity_type="headcount_request", entity_id=req.id, verb="submitted") + log_activity(session, actor_employee_id=actor_employee_id, entity_type="headcount_request", entity_id=req.id, verb="submitted") session.commit() return req @router.patch("/headcount/{request_id}", response_model=HeadcountRequest) -def update_headcount_request(request_id: int, payload: HeadcountRequestUpdate, session: Session = Depends(get_session)): +def update_headcount_request(request_id: int, payload: HeadcountRequestUpdate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): req = session.get(HeadcountRequest, request_id) if not req: raise HTTPException(status_code=404, detail="Request not found") @@ -40,7 +40,7 @@ def update_headcount_request(request_id: int, payload: HeadcountRequestUpdate, s session.add(req) session.commit() session.refresh(req) - log_activity(session, actor_employee_id=req.requested_by_manager_id, entity_type="headcount_request", entity_id=req.id, verb="updated", payload=data) + log_activity(session, actor_employee_id=actor_employee_id, entity_type="headcount_request", entity_id=req.id, verb="updated", payload=data) session.commit() return req @@ -51,11 +51,47 @@ def list_employment_actions(session: Session = Depends(get_session)): @router.post("/actions", response_model=EmploymentAction) -def create_employment_action(payload: EmploymentActionCreate, session: Session = Depends(get_session)): +def create_employment_action(payload: EmploymentActionCreate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): action = EmploymentAction(**payload.model_dump()) session.add(action) session.commit() session.refresh(action) - log_activity(session, actor_employee_id=action.issued_by_employee_id, entity_type="employment_action", entity_id=action.id, verb=action.action_type, payload={"employee_id": action.employee_id}) + log_activity(session, actor_employee_id=actor_employee_id, entity_type="employment_action", entity_id=action.id, verb=action.action_type, payload={"employee_id": action.employee_id}) session.commit() return action + +@router.get("/onboarding", response_model=list[AgentOnboarding]) +def list_agent_onboarding(session: Session = Depends(get_session)): + return session.exec(select(AgentOnboarding).order_by(AgentOnboarding.id.desc())).all() + + +@router.post("/onboarding", response_model=AgentOnboarding) +def create_agent_onboarding(payload: AgentOnboardingCreate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): + item = AgentOnboarding(**payload.model_dump()) + session.add(item) + session.commit() + session.refresh(item) + log_activity(session, actor_employee_id=actor_employee_id, entity_type="agent_onboarding", entity_id=item.id, verb="created", payload={"agent_name": item.agent_name, "status": item.status}) + session.commit() + return item + + +@router.patch("/onboarding/{onboarding_id}", response_model=AgentOnboarding) +def update_agent_onboarding(onboarding_id: int, payload: AgentOnboardingUpdate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): + item = session.get(AgentOnboarding, onboarding_id) + if not item: + raise HTTPException(status_code=404, detail="Onboarding record not found") + + data = payload.model_dump(exclude_unset=True) + for k, v in data.items(): + setattr(item, k, v) + from datetime import datetime + item.updated_at = datetime.utcnow() + + session.add(item) + session.commit() + session.refresh(item) + log_activity(session, actor_employee_id=actor_employee_id, entity_type="agent_onboarding", entity_id=item.id, verb="updated", payload=data) + session.commit() + return item + diff --git a/backend/app/api/org.py b/backend/app/api/org.py index e1060b64..ed94c425 100644 --- a/backend/app/api/org.py +++ b/backend/app/api/org.py @@ -3,7 +3,7 @@ from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException from sqlmodel import Session, select -from app.api.utils import log_activity +from app.api.utils import log_activity, get_actor_employee_id from app.db.session import get_session from app.models.org import Department, Employee from app.schemas.org import DepartmentCreate, DepartmentUpdate, EmployeeCreate, EmployeeUpdate @@ -17,18 +17,18 @@ def list_departments(session: Session = Depends(get_session)): @router.post("/departments", response_model=Department) -def create_department(payload: DepartmentCreate, session: Session = Depends(get_session)): +def create_department(payload: DepartmentCreate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): dept = Department(name=payload.name, head_employee_id=payload.head_employee_id) session.add(dept) session.commit() session.refresh(dept) - log_activity(session, actor_employee_id=None, entity_type="department", entity_id=dept.id, verb="created", payload={"name": dept.name}) + log_activity(session, actor_employee_id=actor_employee_id, entity_type="department", entity_id=dept.id, verb="created", payload={"name": dept.name}) session.commit() return dept @router.patch("/departments/{department_id}", response_model=Department) -def update_department(department_id: int, payload: DepartmentUpdate, session: Session = Depends(get_session)): +def update_department(department_id: int, payload: DepartmentUpdate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): dept = session.get(Department, department_id) if not dept: raise HTTPException(status_code=404, detail="Department not found") @@ -40,7 +40,7 @@ def update_department(department_id: int, payload: DepartmentUpdate, session: Se session.add(dept) session.commit() session.refresh(dept) - log_activity(session, actor_employee_id=None, entity_type="department", entity_id=dept.id, verb="updated", payload=data) + log_activity(session, actor_employee_id=actor_employee_id, entity_type="department", entity_id=dept.id, verb="updated", payload=data) session.commit() return dept @@ -51,18 +51,18 @@ def list_employees(session: Session = Depends(get_session)): @router.post("/employees", response_model=Employee) -def create_employee(payload: EmployeeCreate, session: Session = Depends(get_session)): +def create_employee(payload: EmployeeCreate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): emp = Employee(**payload.model_dump()) session.add(emp) session.commit() session.refresh(emp) - log_activity(session, actor_employee_id=None, entity_type="employee", entity_id=emp.id, verb="created", payload={"name": emp.name, "type": emp.employee_type}) + log_activity(session, actor_employee_id=actor_employee_id, entity_type="employee", entity_id=emp.id, verb="created", payload={"name": emp.name, "type": emp.employee_type}) session.commit() return emp @router.patch("/employees/{employee_id}", response_model=Employee) -def update_employee(employee_id: int, payload: EmployeeUpdate, session: Session = Depends(get_session)): +def update_employee(employee_id: int, payload: EmployeeUpdate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): emp = session.get(Employee, employee_id) if not emp: raise HTTPException(status_code=404, detail="Employee not found") @@ -74,6 +74,6 @@ def update_employee(employee_id: int, payload: EmployeeUpdate, session: Session session.add(emp) session.commit() session.refresh(emp) - log_activity(session, actor_employee_id=None, entity_type="employee", entity_id=emp.id, verb="updated", payload=data) + log_activity(session, actor_employee_id=actor_employee_id, entity_type="employee", entity_id=emp.id, verb="updated", payload=data) session.commit() return emp diff --git a/backend/app/api/projects.py b/backend/app/api/projects.py index 22ecba5f..8548ab65 100644 --- a/backend/app/api/projects.py +++ b/backend/app/api/projects.py @@ -3,7 +3,7 @@ from __future__ import annotations from fastapi import APIRouter, Depends, HTTPException from sqlmodel import Session, select -from app.api.utils import log_activity +from app.api.utils import log_activity, get_actor_employee_id from app.db.session import get_session from app.models.projects import Project, ProjectMember from app.schemas.projects import ProjectCreate, ProjectUpdate @@ -17,18 +17,18 @@ def list_projects(session: Session = Depends(get_session)): @router.post("", response_model=Project) -def create_project(payload: ProjectCreate, session: Session = Depends(get_session)): +def create_project(payload: ProjectCreate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): proj = Project(**payload.model_dump()) session.add(proj) session.commit() session.refresh(proj) - log_activity(session, actor_employee_id=None, entity_type="project", entity_id=proj.id, verb="created", payload={"name": proj.name}) + log_activity(session, actor_employee_id=actor_employee_id, entity_type="project", entity_id=proj.id, verb="created", payload={"name": proj.name}) session.commit() return proj @router.patch("/{project_id}", response_model=Project) -def update_project(project_id: int, payload: ProjectUpdate, session: Session = Depends(get_session)): +def update_project(project_id: int, payload: ProjectUpdate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): proj = session.get(Project, project_id) if not proj: raise HTTPException(status_code=404, detail="Project not found") @@ -40,7 +40,7 @@ def update_project(project_id: int, payload: ProjectUpdate, session: Session = D session.add(proj) session.commit() session.refresh(proj) - log_activity(session, actor_employee_id=None, entity_type="project", entity_id=proj.id, verb="updated", payload=data) + log_activity(session, actor_employee_id=actor_employee_id, entity_type="project", entity_id=proj.id, verb="updated", payload=data) session.commit() return proj @@ -53,7 +53,7 @@ def list_project_members(project_id: int, session: Session = Depends(get_session @router.post("/{project_id}/members", response_model=ProjectMember) -def add_project_member(project_id: int, payload: ProjectMember, session: Session = Depends(get_session)): +def add_project_member(project_id: int, payload: ProjectMember, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): existing = session.exec(select(ProjectMember).where(ProjectMember.project_id == project_id, ProjectMember.employee_id == payload.employee_id)).first() if existing: raise HTTPException(status_code=409, detail="Member already added") @@ -63,7 +63,7 @@ def add_project_member(project_id: int, payload: ProjectMember, session: Session session.refresh(member) log_activity( session, - actor_employee_id=None, + actor_employee_id=actor_employee_id, entity_type="project_member", entity_id=member.id, verb="added", @@ -74,7 +74,7 @@ def add_project_member(project_id: int, payload: ProjectMember, session: Session @router.delete("/{project_id}/members/{member_id}") -def remove_project_member(project_id: int, member_id: int, session: Session = Depends(get_session)): +def remove_project_member(project_id: int, member_id: int, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): member = session.get(ProjectMember, member_id) if not member or member.project_id != project_id: raise HTTPException(status_code=404, detail="Project member not found") @@ -82,7 +82,7 @@ def remove_project_member(project_id: int, member_id: int, session: Session = De session.commit() log_activity( session, - actor_employee_id=None, + actor_employee_id=actor_employee_id, entity_type="project_member", entity_id=member_id, verb="removed", @@ -93,7 +93,7 @@ def remove_project_member(project_id: int, member_id: int, session: Session = De @router.patch("/{project_id}/members/{member_id}", response_model=ProjectMember) -def update_project_member(project_id: int, member_id: int, payload: ProjectMember, session: Session = Depends(get_session)): +def update_project_member(project_id: int, member_id: int, payload: ProjectMember, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): member = session.get(ProjectMember, member_id) if not member or member.project_id != project_id: raise HTTPException(status_code=404, detail="Project member not found") @@ -106,7 +106,7 @@ def update_project_member(project_id: int, member_id: int, payload: ProjectMembe session.refresh(member) log_activity( session, - actor_employee_id=None, + actor_employee_id=actor_employee_id, entity_type="project_member", entity_id=member.id, verb="updated", diff --git a/backend/app/api/utils.py b/backend/app/api/utils.py index 4ce1e6c0..ac5108dd 100644 --- a/backend/app/api/utils.py +++ b/backend/app/api/utils.py @@ -3,6 +3,7 @@ from __future__ import annotations import json from typing import Any +from fastapi import Header, HTTPException from sqlmodel import Session from app.models.activity import Activity @@ -26,3 +27,11 @@ def log_activity( payload_json=json.dumps(payload) if payload is not None else None, ) ) + + +def get_actor_employee_id( + x_actor_employee_id: int | None = Header(default=None, alias="X-Actor-Employee-Id"), +) -> int: + if x_actor_employee_id is None: + raise HTTPException(status_code=400, detail="X-Actor-Employee-Id required") + return x_actor_employee_id diff --git a/backend/app/api/work.py b/backend/app/api/work.py index 02790d49..b80fe007 100644 --- a/backend/app/api/work.py +++ b/backend/app/api/work.py @@ -5,7 +5,7 @@ from datetime import datetime from fastapi import APIRouter, Depends, HTTPException from sqlmodel import Session, select -from app.api.utils import log_activity +from app.api.utils import log_activity, get_actor_employee_id from app.db.session import get_session from app.models.work import Task, TaskComment from app.schemas.work import TaskCommentCreate, TaskCreate, TaskUpdate @@ -24,7 +24,9 @@ def list_tasks(project_id: int | None = None, session: Session = Depends(get_ses @router.post("/tasks", response_model=Task) -def create_task(payload: TaskCreate, session: Session = Depends(get_session)): +def create_task(payload: TaskCreate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): + if payload.created_by_employee_id is None: + payload = TaskCreate(**{**payload.model_dump(), "created_by_employee_id": actor_employee_id}) task = Task(**payload.model_dump()) if task.status not in ALLOWED_STATUSES: raise HTTPException(status_code=400, detail="Invalid status") @@ -34,7 +36,7 @@ def create_task(payload: TaskCreate, session: Session = Depends(get_session)): session.refresh(task) log_activity( session, - actor_employee_id=task.created_by_employee_id, + actor_employee_id=actor_employee_id, entity_type="task", entity_id=task.id, verb="created", @@ -45,7 +47,7 @@ def create_task(payload: TaskCreate, session: Session = Depends(get_session)): @router.patch("/tasks/{task_id}", response_model=Task) -def update_task(task_id: int, payload: TaskUpdate, session: Session = Depends(get_session)): +def update_task(task_id: int, payload: TaskUpdate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): task = session.get(Task, task_id) if not task: raise HTTPException(status_code=404, detail="Task not found") @@ -60,19 +62,19 @@ def update_task(task_id: int, payload: TaskUpdate, session: Session = Depends(ge session.add(task) session.commit() session.refresh(task) - log_activity(session, actor_employee_id=None, entity_type="task", entity_id=task.id, verb="updated", payload=data) + log_activity(session, actor_employee_id=actor_employee_id, entity_type="task", entity_id=task.id, verb="updated", payload=data) session.commit() return task @router.delete("/tasks/{task_id}") -def delete_task(task_id: int, session: Session = Depends(get_session)): +def delete_task(task_id: int, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): task = session.get(Task, task_id) if not task: raise HTTPException(status_code=404, detail="Task not found") session.delete(task) session.commit() - log_activity(session, actor_employee_id=None, entity_type="task", entity_id=task_id, verb="deleted") + log_activity(session, actor_employee_id=actor_employee_id, entity_type="task", entity_id=task_id, verb="deleted") session.commit() return {"ok": True} @@ -83,11 +85,13 @@ def list_task_comments(task_id: int, session: Session = Depends(get_session)): @router.post("/task-comments", response_model=TaskComment) -def create_task_comment(payload: TaskCommentCreate, session: Session = Depends(get_session)): +def create_task_comment(payload: TaskCommentCreate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)): + if payload.author_employee_id is None: + payload = TaskCommentCreate(**{**payload.model_dump(), "author_employee_id": actor_employee_id}) c = TaskComment(**payload.model_dump()) session.add(c) session.commit() session.refresh(c) - log_activity(session, actor_employee_id=c.author_employee_id, entity_type="task", entity_id=c.task_id, verb="commented") + log_activity(session, actor_employee_id=actor_employee_id, entity_type="task", entity_id=c.task_id, verb="commented") session.commit() return c diff --git a/backend/app/models/hr.py b/backend/app/models/hr.py index 2b3f59a1..4f8adc19 100644 --- a/backend/app/models/hr.py +++ b/backend/app/models/hr.py @@ -33,3 +33,23 @@ class EmploymentAction(SQLModel, table=True): notes: str | None = None created_at: datetime = Field(default_factory=datetime.utcnow) + + +class AgentOnboarding(SQLModel, table=True): + __tablename__ = "agent_onboardings" + + id: int | None = Field(default=None, primary_key=True) + agent_name: str + role_title: str + prompt: str + cron_interval_ms: int | None = None + tools_json: str | None = None + owner_hr_id: int | None = Field(default=None, foreign_key="employees.id") + + status: str = Field(default="planned") # planned|spawning|spawned|verified|blocked + spawned_agent_id: str | None = None + session_key: str | None = None + + notes: str | None = None + created_at: datetime = Field(default_factory=datetime.utcnow) + updated_at: datetime = Field(default_factory=datetime.utcnow) diff --git a/backend/app/schemas/hr.py b/backend/app/schemas/hr.py index 78dceb4a..691e44f0 100644 --- a/backend/app/schemas/hr.py +++ b/backend/app/schemas/hr.py @@ -22,3 +22,30 @@ class EmploymentActionCreate(SQLModel): issued_by_employee_id: int action_type: str notes: str | None = None + + +class AgentOnboardingCreate(SQLModel): + agent_name: str + role_title: str + prompt: str + cron_interval_ms: int | None = None + tools_json: str | None = None + owner_hr_id: int | None = None + status: str = "planned" + spawned_agent_id: str | None = None + session_key: str | None = None + notes: str | None = None + + +class AgentOnboardingUpdate(SQLModel): + agent_name: str | None = None + role_title: str | None = None + prompt: str | None = None + cron_interval_ms: int | None = None + tools_json: str | None = None + owner_hr_id: int | None = None + status: str | None = None + spawned_agent_id: str | None = None + session_key: str | None = None + notes: str | None = None + diff --git a/frontend/src/api/generated/hr/hr.ts b/frontend/src/api/generated/hr/hr.ts index b29d44e4..c19ad6c6 100644 --- a/frontend/src/api/generated/hr/hr.ts +++ b/frontend/src/api/generated/hr/hr.ts @@ -21,6 +21,9 @@ import type { } from "@tanstack/react-query"; import type { + AgentOnboarding, + AgentOnboardingCreate, + AgentOnboardingUpdate, EmploymentAction, EmploymentActionCreate, HTTPValidationError, @@ -748,3 +751,434 @@ export const useCreateEmploymentActionHrActionsPost = < queryClient, ); }; +/** + * @summary List Agent Onboarding + */ +export type listAgentOnboardingHrOnboardingGetResponse200 = { + data: AgentOnboarding[]; + status: 200; +}; + +export type listAgentOnboardingHrOnboardingGetResponseSuccess = + listAgentOnboardingHrOnboardingGetResponse200 & { + headers: Headers; + }; +export type listAgentOnboardingHrOnboardingGetResponse = + listAgentOnboardingHrOnboardingGetResponseSuccess; + +export const getListAgentOnboardingHrOnboardingGetUrl = () => { + return `/hr/onboarding`; +}; + +export const listAgentOnboardingHrOnboardingGet = async ( + options?: RequestInit, +): Promise => { + return customFetch( + getListAgentOnboardingHrOnboardingGetUrl(), + { + ...options, + method: "GET", + }, + ); +}; + +export const getListAgentOnboardingHrOnboardingGetQueryKey = () => { + return [`/hr/onboarding`] as const; +}; + +export const getListAgentOnboardingHrOnboardingGetQueryOptions = < + TData = Awaited>, + TError = unknown, +>(options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; +}) => { + const { query: queryOptions, request: requestOptions } = options ?? {}; + + const queryKey = + queryOptions?.queryKey ?? getListAgentOnboardingHrOnboardingGetQueryKey(); + + const queryFn: QueryFunction< + Awaited> + > = ({ signal }) => + listAgentOnboardingHrOnboardingGet({ signal, ...requestOptions }); + + return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type ListAgentOnboardingHrOnboardingGetQueryResult = NonNullable< + Awaited> +>; +export type ListAgentOnboardingHrOnboardingGetQueryError = unknown; + +export function useListAgentOnboardingHrOnboardingGet< + TData = Awaited>, + TError = unknown, +>( + options: { + query: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + DefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useListAgentOnboardingHrOnboardingGet< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + > & + Pick< + UndefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + "initialData" + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useListAgentOnboardingHrOnboardingGet< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +/** + * @summary List Agent Onboarding + */ + +export function useListAgentOnboardingHrOnboardingGet< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseQueryOptions< + Awaited>, + TError, + TData + > + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = + getListAgentOnboardingHrOnboardingGetQueryOptions(options); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} + +/** + * @summary Create Agent Onboarding + */ +export type createAgentOnboardingHrOnboardingPostResponse200 = { + data: AgentOnboarding; + status: 200; +}; + +export type createAgentOnboardingHrOnboardingPostResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type createAgentOnboardingHrOnboardingPostResponseSuccess = + createAgentOnboardingHrOnboardingPostResponse200 & { + headers: Headers; + }; +export type createAgentOnboardingHrOnboardingPostResponseError = + createAgentOnboardingHrOnboardingPostResponse422 & { + headers: Headers; + }; + +export type createAgentOnboardingHrOnboardingPostResponse = + | createAgentOnboardingHrOnboardingPostResponseSuccess + | createAgentOnboardingHrOnboardingPostResponseError; + +export const getCreateAgentOnboardingHrOnboardingPostUrl = () => { + return `/hr/onboarding`; +}; + +export const createAgentOnboardingHrOnboardingPost = async ( + agentOnboardingCreate: AgentOnboardingCreate, + options?: RequestInit, +): Promise => { + return customFetch( + getCreateAgentOnboardingHrOnboardingPostUrl(), + { + ...options, + method: "POST", + headers: { "Content-Type": "application/json", ...options?.headers }, + body: JSON.stringify(agentOnboardingCreate), + }, + ); +}; + +export const getCreateAgentOnboardingHrOnboardingPostMutationOptions = < + TError = HTTPValidationError, + TContext = unknown, +>(options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { data: AgentOnboardingCreate }, + TContext + >; + request?: SecondParameter; +}): UseMutationOptions< + Awaited>, + TError, + { data: AgentOnboardingCreate }, + TContext +> => { + const mutationKey = ["createAgentOnboardingHrOnboardingPost"]; + const { mutation: mutationOptions, request: requestOptions } = options + ? options.mutation && + "mutationKey" in options.mutation && + options.mutation.mutationKey + ? options + : { ...options, mutation: { ...options.mutation, mutationKey } } + : { mutation: { mutationKey }, request: undefined }; + + const mutationFn: MutationFunction< + Awaited>, + { data: AgentOnboardingCreate } + > = (props) => { + const { data } = props ?? {}; + + return createAgentOnboardingHrOnboardingPost(data, requestOptions); + }; + + return { mutationFn, ...mutationOptions }; +}; + +export type CreateAgentOnboardingHrOnboardingPostMutationResult = NonNullable< + Awaited> +>; +export type CreateAgentOnboardingHrOnboardingPostMutationBody = + AgentOnboardingCreate; +export type CreateAgentOnboardingHrOnboardingPostMutationError = + HTTPValidationError; + +/** + * @summary Create Agent Onboarding + */ +export const useCreateAgentOnboardingHrOnboardingPost = < + TError = HTTPValidationError, + TContext = unknown, +>( + options?: { + mutation?: UseMutationOptions< + Awaited>, + TError, + { data: AgentOnboardingCreate }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited>, + TError, + { data: AgentOnboardingCreate }, + TContext +> => { + return useMutation( + getCreateAgentOnboardingHrOnboardingPostMutationOptions(options), + queryClient, + ); +}; +/** + * @summary Update Agent Onboarding + */ +export type updateAgentOnboardingHrOnboardingOnboardingIdPatchResponse200 = { + data: AgentOnboarding; + status: 200; +}; + +export type updateAgentOnboardingHrOnboardingOnboardingIdPatchResponse422 = { + data: HTTPValidationError; + status: 422; +}; + +export type updateAgentOnboardingHrOnboardingOnboardingIdPatchResponseSuccess = + updateAgentOnboardingHrOnboardingOnboardingIdPatchResponse200 & { + headers: Headers; + }; +export type updateAgentOnboardingHrOnboardingOnboardingIdPatchResponseError = + updateAgentOnboardingHrOnboardingOnboardingIdPatchResponse422 & { + headers: Headers; + }; + +export type updateAgentOnboardingHrOnboardingOnboardingIdPatchResponse = + | updateAgentOnboardingHrOnboardingOnboardingIdPatchResponseSuccess + | updateAgentOnboardingHrOnboardingOnboardingIdPatchResponseError; + +export const getUpdateAgentOnboardingHrOnboardingOnboardingIdPatchUrl = ( + onboardingId: number, +) => { + return `/hr/onboarding/${onboardingId}`; +}; + +export const updateAgentOnboardingHrOnboardingOnboardingIdPatch = async ( + onboardingId: number, + agentOnboardingUpdate: AgentOnboardingUpdate, + options?: RequestInit, +): Promise => { + return customFetch( + getUpdateAgentOnboardingHrOnboardingOnboardingIdPatchUrl(onboardingId), + { + ...options, + method: "PATCH", + headers: { "Content-Type": "application/json", ...options?.headers }, + body: JSON.stringify(agentOnboardingUpdate), + }, + ); +}; + +export const getUpdateAgentOnboardingHrOnboardingOnboardingIdPatchMutationOptions = + (options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType + >, + TError, + { onboardingId: number; data: AgentOnboardingUpdate }, + TContext + >; + request?: SecondParameter; + }): UseMutationOptions< + Awaited< + ReturnType + >, + TError, + { onboardingId: number; data: AgentOnboardingUpdate }, + TContext + > => { + const mutationKey = ["updateAgentOnboardingHrOnboardingOnboardingIdPatch"]; + const { mutation: mutationOptions, request: requestOptions } = options + ? options.mutation && + "mutationKey" in options.mutation && + options.mutation.mutationKey + ? options + : { ...options, mutation: { ...options.mutation, mutationKey } } + : { mutation: { mutationKey }, request: undefined }; + + const mutationFn: MutationFunction< + Awaited< + ReturnType + >, + { onboardingId: number; data: AgentOnboardingUpdate } + > = (props) => { + const { onboardingId, data } = props ?? {}; + + return updateAgentOnboardingHrOnboardingOnboardingIdPatch( + onboardingId, + data, + requestOptions, + ); + }; + + return { mutationFn, ...mutationOptions }; + }; + +export type UpdateAgentOnboardingHrOnboardingOnboardingIdPatchMutationResult = + NonNullable< + Awaited< + ReturnType + > + >; +export type UpdateAgentOnboardingHrOnboardingOnboardingIdPatchMutationBody = + AgentOnboardingUpdate; +export type UpdateAgentOnboardingHrOnboardingOnboardingIdPatchMutationError = + HTTPValidationError; + +/** + * @summary Update Agent Onboarding + */ +export const useUpdateAgentOnboardingHrOnboardingOnboardingIdPatch = < + TError = HTTPValidationError, + TContext = unknown, +>( + options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType + >, + TError, + { onboardingId: number; data: AgentOnboardingUpdate }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited< + ReturnType + >, + TError, + { onboardingId: number; data: AgentOnboardingUpdate }, + TContext +> => { + return useMutation( + getUpdateAgentOnboardingHrOnboardingOnboardingIdPatchMutationOptions( + options, + ), + queryClient, + ); +}; diff --git a/frontend/src/api/generated/model/agentOnboarding.ts b/frontend/src/api/generated/model/agentOnboarding.ts new file mode 100644 index 00000000..fb5e2b03 --- /dev/null +++ b/frontend/src/api/generated/model/agentOnboarding.ts @@ -0,0 +1,22 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * OpenClaw Agency API + * OpenAPI spec version: 0.3.0 + */ + +export interface AgentOnboarding { + id?: number | null; + agent_name: string; + role_title: string; + prompt: string; + cron_interval_ms?: number | null; + tools_json?: string | null; + owner_hr_id?: number | null; + status?: string; + spawned_agent_id?: string | null; + session_key?: string | null; + notes?: string | null; + created_at?: string; + updated_at?: string; +} diff --git a/frontend/src/api/generated/model/agentOnboardingCreate.ts b/frontend/src/api/generated/model/agentOnboardingCreate.ts new file mode 100644 index 00000000..2df02fee --- /dev/null +++ b/frontend/src/api/generated/model/agentOnboardingCreate.ts @@ -0,0 +1,19 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * OpenClaw Agency API + * OpenAPI spec version: 0.3.0 + */ + +export interface AgentOnboardingCreate { + agent_name: string; + role_title: string; + prompt: string; + cron_interval_ms?: number | null; + tools_json?: string | null; + owner_hr_id?: number | null; + status?: string; + spawned_agent_id?: string | null; + session_key?: string | null; + notes?: string | null; +} diff --git a/frontend/src/api/generated/model/agentOnboardingUpdate.ts b/frontend/src/api/generated/model/agentOnboardingUpdate.ts new file mode 100644 index 00000000..f64b351c --- /dev/null +++ b/frontend/src/api/generated/model/agentOnboardingUpdate.ts @@ -0,0 +1,19 @@ +/** + * Generated by orval v8.2.0 🍺 + * Do not edit manually. + * OpenClaw Agency API + * OpenAPI spec version: 0.3.0 + */ + +export interface AgentOnboardingUpdate { + agent_name?: string | null; + role_title?: string | null; + prompt?: string | null; + cron_interval_ms?: number | null; + tools_json?: string | null; + owner_hr_id?: number | null; + status?: string | null; + spawned_agent_id?: string | null; + session_key?: string | null; + notes?: string | null; +} diff --git a/frontend/src/api/generated/model/index.ts b/frontend/src/api/generated/model/index.ts index 6e2da782..f1e08e22 100644 --- a/frontend/src/api/generated/model/index.ts +++ b/frontend/src/api/generated/model/index.ts @@ -5,6 +5,9 @@ * OpenAPI spec version: 0.3.0 */ +export * from "./agentOnboarding"; +export * from "./agentOnboardingCreate"; +export * from "./agentOnboardingUpdate"; export * from "./department"; export * from "./departmentCreate"; export * from "./departmentUpdate"; diff --git a/frontend/src/api/mutator.ts b/frontend/src/api/mutator.ts index 54ffd1a4..fbe9ae6a 100644 --- a/frontend/src/api/mutator.ts +++ b/frontend/src/api/mutator.ts @@ -1,3 +1,17 @@ + +function getActorId(): string | undefined { + if (typeof window !== "undefined") { + const stored = window.localStorage.getItem("actor_employee_id"); + if (stored) return stored; + const env = process.env.NEXT_PUBLIC_ACTOR_EMPLOYEE_ID; + if (env) { + window.localStorage.setItem("actor_employee_id", env); + return env; + } + return undefined; + } + return process.env.NEXT_PUBLIC_ACTOR_EMPLOYEE_ID; +} export async function customFetch( url: string, options: RequestInit, @@ -9,6 +23,7 @@ export async function customFetch( ...options, headers: { "Content-Type": "application/json", + ...(getActorId() ? { "X-Actor-Employee-Id": String(getActorId()) } : {}), ...(options.headers ?? {}), }, }); diff --git a/frontend/src/app/_components/Shell.tsx b/frontend/src/app/_components/Shell.tsx index 48503dce..68d875ed 100644 --- a/frontend/src/app/_components/Shell.tsx +++ b/frontend/src/app/_components/Shell.tsx @@ -2,6 +2,7 @@ import Link from "next/link"; import { usePathname } from "next/navigation"; +import { useEffect, useState } from "react"; import styles from "./Shell.module.css"; const NAV = [ @@ -14,6 +15,16 @@ const NAV = [ export function Shell({ children }: { children: React.ReactNode }) { const path = usePathname(); + const [actorId, setActorId] = useState(""); + + useEffect(() => { + try { + const stored = window.localStorage.getItem("actor_employee_id"); + if (stored) setActorId(stored); + } catch { + // ignore + } + }, []); return (