diff --git a/backend/app/api/org.py b/backend/app/api/org.py index df8acc4d..571a4894 100644 --- a/backend/app/api/org.py +++ b/backend/app/api/org.py @@ -24,7 +24,17 @@ def _default_agent_prompt(emp: Employee) -> str: return ( f"You are {emp.name}, an AI agent employee in Mission Control.\n" + f"Your employee_id is {emp.id}.\n" f"Title: {title}. Department id: {dept}.\n\n" + "Mission Control API access (no UI):\n" + "- Base URL: http://127.0.0.1:8000 (if running locally) OR http://:8000\n" + "- Auth: none. REQUIRED header on write operations: X-Actor-Employee-Id: \n" + f" For you: X-Actor-Employee-Id: {emp.id}\n\n" + "Common endpoints (JSON):\n" + "- GET /tasks, POST /tasks\n" + "- GET /task-comments, POST /task-comments\n" + "- GET /projects, GET /employees, GET /departments\n" + "- OpenAPI schema: GET /openapi.json\n\n" "Rules:\n" "- Use the Mission Control API only (no UI).\n" "- When notified about tasks/comments, respond with concise, actionable updates.\n" diff --git a/frontend/src/api/generated/model/employee.ts b/frontend/src/api/generated/model/employee.ts index 159ac3fd..893f4eb5 100644 --- a/frontend/src/api/generated/model/employee.ts +++ b/frontend/src/api/generated/model/employee.ts @@ -13,4 +13,6 @@ export interface Employee { manager_id?: number | null; title?: string | null; status?: string; + openclaw_session_key?: string | null; + notify_enabled?: boolean; } diff --git a/frontend/src/api/generated/model/employeeCreate.ts b/frontend/src/api/generated/model/employeeCreate.ts index a6a58771..74dcd459 100644 --- a/frontend/src/api/generated/model/employeeCreate.ts +++ b/frontend/src/api/generated/model/employeeCreate.ts @@ -12,4 +12,6 @@ export interface EmployeeCreate { manager_id?: number | null; title?: string | null; status?: string; + openclaw_session_key?: string | null; + notify_enabled?: boolean; } diff --git a/frontend/src/api/generated/model/employeeUpdate.ts b/frontend/src/api/generated/model/employeeUpdate.ts index d2a1706b..4cb343d3 100644 --- a/frontend/src/api/generated/model/employeeUpdate.ts +++ b/frontend/src/api/generated/model/employeeUpdate.ts @@ -12,4 +12,6 @@ export interface EmployeeUpdate { manager_id?: number | null; title?: string | null; status?: string | null; + openclaw_session_key?: string | null; + notify_enabled?: boolean | null; } diff --git a/frontend/src/api/generated/model/taskComment.ts b/frontend/src/api/generated/model/taskComment.ts index 232db265..2d5713f1 100644 --- a/frontend/src/api/generated/model/taskComment.ts +++ b/frontend/src/api/generated/model/taskComment.ts @@ -9,6 +9,7 @@ export interface TaskComment { id?: number | null; task_id: number; author_employee_id?: number | null; + reply_to_comment_id?: number | null; body: string; created_at?: string; } diff --git a/frontend/src/api/generated/model/taskCommentCreate.ts b/frontend/src/api/generated/model/taskCommentCreate.ts index 01342715..102180b2 100644 --- a/frontend/src/api/generated/model/taskCommentCreate.ts +++ b/frontend/src/api/generated/model/taskCommentCreate.ts @@ -8,5 +8,6 @@ export interface TaskCommentCreate { task_id: number; author_employee_id?: number | null; + reply_to_comment_id?: number | null; body: string; } diff --git a/frontend/src/api/generated/org/org.ts b/frontend/src/api/generated/org/org.ts index 359bbb3d..4c7c3f4d 100644 --- a/frontend/src/api/generated/org/org.ts +++ b/frontend/src/api/generated/org/org.ts @@ -207,6 +207,10 @@ export function useListDepartmentsDepartmentsGet< } /** + * Create a department. + +Important: keep the operation atomic. We flush to get dept.id, log the activity, +then commit once. We also translate common DB integrity errors into 409s. * @summary Create Department */ export type createDepartmentDepartmentsPostResponse200 = { @@ -863,3 +867,299 @@ export const useUpdateEmployeeEmployeesEmployeeIdPatch = < queryClient, ); }; +/** + * @summary Provision Employee Agent + */ +export type provisionEmployeeAgentEmployeesEmployeeIdProvisionPostResponse200 = + { + data: Employee; + status: 200; + }; + +export type provisionEmployeeAgentEmployeesEmployeeIdProvisionPostResponse422 = + { + data: HTTPValidationError; + status: 422; + }; + +export type provisionEmployeeAgentEmployeesEmployeeIdProvisionPostResponseSuccess = + provisionEmployeeAgentEmployeesEmployeeIdProvisionPostResponse200 & { + headers: Headers; + }; +export type provisionEmployeeAgentEmployeesEmployeeIdProvisionPostResponseError = + provisionEmployeeAgentEmployeesEmployeeIdProvisionPostResponse422 & { + headers: Headers; + }; + +export type provisionEmployeeAgentEmployeesEmployeeIdProvisionPostResponse = + | provisionEmployeeAgentEmployeesEmployeeIdProvisionPostResponseSuccess + | provisionEmployeeAgentEmployeesEmployeeIdProvisionPostResponseError; + +export const getProvisionEmployeeAgentEmployeesEmployeeIdProvisionPostUrl = ( + employeeId: number, +) => { + return `/employees/${employeeId}/provision`; +}; + +export const provisionEmployeeAgentEmployeesEmployeeIdProvisionPost = async ( + employeeId: number, + options?: RequestInit, +): Promise => { + return customFetch( + getProvisionEmployeeAgentEmployeesEmployeeIdProvisionPostUrl(employeeId), + { + ...options, + method: "POST", + }, + ); +}; + +export const getProvisionEmployeeAgentEmployeesEmployeeIdProvisionPostMutationOptions = + (options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType< + typeof provisionEmployeeAgentEmployeesEmployeeIdProvisionPost + > + >, + TError, + { employeeId: number }, + TContext + >; + request?: SecondParameter; + }): UseMutationOptions< + Awaited< + ReturnType + >, + TError, + { employeeId: number }, + TContext + > => { + const mutationKey = [ + "provisionEmployeeAgentEmployeesEmployeeIdProvisionPost", + ]; + 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< + typeof provisionEmployeeAgentEmployeesEmployeeIdProvisionPost + > + >, + { employeeId: number } + > = (props) => { + const { employeeId } = props ?? {}; + + return provisionEmployeeAgentEmployeesEmployeeIdProvisionPost( + employeeId, + requestOptions, + ); + }; + + return { mutationFn, ...mutationOptions }; + }; + +export type ProvisionEmployeeAgentEmployeesEmployeeIdProvisionPostMutationResult = + NonNullable< + Awaited< + ReturnType + > + >; + +export type ProvisionEmployeeAgentEmployeesEmployeeIdProvisionPostMutationError = + HTTPValidationError; + +/** + * @summary Provision Employee Agent + */ +export const useProvisionEmployeeAgentEmployeesEmployeeIdProvisionPost = < + TError = HTTPValidationError, + TContext = unknown, +>( + options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType< + typeof provisionEmployeeAgentEmployeesEmployeeIdProvisionPost + > + >, + TError, + { employeeId: number }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited< + ReturnType + >, + TError, + { employeeId: number }, + TContext +> => { + return useMutation( + getProvisionEmployeeAgentEmployeesEmployeeIdProvisionPostMutationOptions( + options, + ), + queryClient, + ); +}; +/** + * @summary Deprovision Employee Agent + */ +export type deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostResponse200 = + { + data: Employee; + status: 200; + }; + +export type deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostResponse422 = + { + data: HTTPValidationError; + status: 422; + }; + +export type deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostResponseSuccess = + deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostResponse200 & { + headers: Headers; + }; +export type deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostResponseError = + deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostResponse422 & { + headers: Headers; + }; + +export type deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostResponse = + | deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostResponseSuccess + | deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostResponseError; + +export const getDeprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostUrl = + (employeeId: number) => { + return `/employees/${employeeId}/deprovision`; + }; + +export const deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPost = + async ( + employeeId: number, + options?: RequestInit, + ): Promise => { + return customFetch( + getDeprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostUrl( + employeeId, + ), + { + ...options, + method: "POST", + }, + ); + }; + +export const getDeprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostMutationOptions = + (options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType< + typeof deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPost + > + >, + TError, + { employeeId: number }, + TContext + >; + request?: SecondParameter; + }): UseMutationOptions< + Awaited< + ReturnType< + typeof deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPost + > + >, + TError, + { employeeId: number }, + TContext + > => { + const mutationKey = [ + "deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPost", + ]; + 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< + typeof deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPost + > + >, + { employeeId: number } + > = (props) => { + const { employeeId } = props ?? {}; + + return deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPost( + employeeId, + requestOptions, + ); + }; + + return { mutationFn, ...mutationOptions }; + }; + +export type DeprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostMutationResult = + NonNullable< + Awaited< + ReturnType< + typeof deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPost + > + > + >; + +export type DeprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostMutationError = + HTTPValidationError; + +/** + * @summary Deprovision Employee Agent + */ +export const useDeprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPost = < + TError = HTTPValidationError, + TContext = unknown, +>( + options?: { + mutation?: UseMutationOptions< + Awaited< + ReturnType< + typeof deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPost + > + >, + TError, + { employeeId: number }, + TContext + >; + request?: SecondParameter; + }, + queryClient?: QueryClient, +): UseMutationResult< + Awaited< + ReturnType< + typeof deprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPost + > + >, + TError, + { employeeId: number }, + TContext +> => { + return useMutation( + getDeprovisionEmployeeAgentEmployeesEmployeeIdDeprovisionPostMutationOptions( + options, + ), + queryClient, + ); +}; diff --git a/frontend/src/api/generated/projects/projects.ts b/frontend/src/api/generated/projects/projects.ts index ac9835a6..604c9135 100644 --- a/frontend/src/api/generated/projects/projects.ts +++ b/frontend/src/api/generated/projects/projects.ts @@ -204,6 +204,10 @@ export function useListProjectsProjectsGet< } /** + * Create a project. + +Keep operation atomic: flush to get id, log activity, then commit once. +Translate DB integrity errors to 409s. * @summary Create Project */ export type createProjectProjectsPostResponse200 = { diff --git a/frontend/src/app/_components/Shell.tsx b/frontend/src/app/_components/Shell.tsx index 577c3a9e..6caaa052 100644 --- a/frontend/src/app/_components/Shell.tsx +++ b/frontend/src/app/_components/Shell.tsx @@ -11,7 +11,6 @@ const NAV = [ { href: "/kanban", label: "Kanban" }, { href: "/departments", label: "Departments" }, { href: "/people", label: "People" }, - { href: "/hr", label: "HR" }, ]; export function Shell({ children }: { children: React.ReactNode }) { diff --git a/frontend/src/app/departments/page.tsx b/frontend/src/app/departments/page.tsx index 0ca934d7..9e5d8e97 100644 --- a/frontend/src/app/departments/page.tsx +++ b/frontend/src/app/departments/page.tsx @@ -20,10 +20,10 @@ export default function DepartmentsPage() { const [headId, setHeadId] = useState(""); const departments = useListDepartmentsDepartmentsGet(); - const departmentList = departments.data ?? []; + const departmentList = departments.data?.status === 200 ? departments.data.data : []; const employees = useListEmployeesEmployeesGet(); - const employeeList = employees.data ?? []; + const employeeList = employees.data?.status === 200 ? employees.data.data : []; const createDepartment = useCreateDepartmentDepartmentsPost({ mutation: { diff --git a/frontend/src/app/hr/page.tsx b/frontend/src/app/hr/page.tsx deleted file mode 100644 index cc2cfa28..00000000 --- a/frontend/src/app/hr/page.tsx +++ /dev/null @@ -1,355 +0,0 @@ -"use client"; - -import { useState } from "react"; - -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { Input } from "@/components/ui/input"; - -import { Select } from "@/components/ui/select"; -import { Textarea } from "@/components/ui/textarea"; - -import { - useCreateHeadcountRequestHrHeadcountPost, - useCreateEmploymentActionHrActionsPost, - useListHeadcountRequestsHrHeadcountGet, - useListEmploymentActionsHrActionsGet, - useListAgentOnboardingHrOnboardingGet, - useCreateAgentOnboardingHrOnboardingPost, - useUpdateAgentOnboardingHrOnboardingOnboardingIdPatch, -} from "@/api/generated/hr/hr"; -import { useListDepartmentsDepartmentsGet, useListEmployeesEmployeesGet } from "@/api/generated/org/org"; - -export default function HRPage() { - const departments = useListDepartmentsDepartmentsGet(); - const departmentList = departments.data ?? []; - const employees = useListEmployeesEmployeesGet(); - const employeeList = employees.data ?? []; - - const headcount = useListHeadcountRequestsHrHeadcountGet(); - const actions = useListEmploymentActionsHrActionsGet(); - const onboarding = useListAgentOnboardingHrOnboardingGet(); - const headcountList = headcount.data ?? []; - const actionList = actions.data ?? []; - const onboardingList = onboarding.data ?? []; - - const [hcDeptId, setHcDeptId] = useState(""); - const [hcManagerId, setHcManagerId] = useState(""); - const [hcRole, setHcRole] = useState(""); - const [hcType, setHcType] = useState<"human" | "agent">("human"); - const [hcQty, setHcQty] = useState("1"); - const [hcJust, setHcJust] = useState(""); - - const [actEmployeeId, setActEmployeeId] = useState(""); - const [actIssuerId, setActIssuerId] = useState(""); - const [actType, setActType] = useState("praise"); - const [actNotes, setActNotes] = useState(""); - - - const [onboardAgentName, setOnboardAgentName] = useState(""); - const [onboardRole, setOnboardRole] = useState(""); - const [onboardPrompt, setOnboardPrompt] = useState(""); - const [onboardCronMs, setOnboardCronMs] = useState(""); - const [onboardTools, setOnboardTools] = useState(""); - const [onboardOwnerId, setOnboardOwnerId] = useState(""); - const [onboardNotes, setOnboardNotes] = useState(""); - const createHeadcount = useCreateHeadcountRequestHrHeadcountPost({ - mutation: { - onSuccess: () => { - setHcRole(""); - setHcJust(""); - setHcQty("1"); - headcount.refetch(); - }, - }, - }); - - const createAction = useCreateEmploymentActionHrActionsPost({ - mutation: { - onSuccess: () => { - setActNotes(""); - actions.refetch(); - }, - }, - }); - - const createOnboarding = useCreateAgentOnboardingHrOnboardingPost({ - mutation: { - onSuccess: () => { - setOnboardAgentName(""); - setOnboardRole(""); - setOnboardPrompt(""); - setOnboardCronMs(""); - setOnboardTools(""); - setOnboardOwnerId(""); - setOnboardNotes(""); - onboarding.refetch(); - }, - }, - }); - - const updateOnboarding = useUpdateAgentOnboardingHrOnboardingOnboardingIdPatch({ - mutation: { - onSuccess: () => onboarding.refetch(), - }, - }); - - return ( -
- {headcount.isLoading || actions.isLoading || onboarding.isLoading ? ( -
Loading…
- ) : null} - {headcount.error ?
{(headcount.error as Error).message}
: null} - {actions.error ?
{(actions.error as Error).message}
: null} - {onboarding.error ?
{(onboarding.error as Error).message}
: null} -
-
-

HR

-

Headcount requests and employment actions.

-
- -
- -
- - - Headcount request - Managers request; HR fulfills later. - - - {departments.isLoading ?
Loading departments…
: null} - {departments.error ?
{(departments.error as Error).message}
: null} - {employees.isLoading ?
Loading employees…
: null} - {employees.error ?
{(employees.error as Error).message}
: null} - - - setHcRole(e.target.value)} /> -
- - setHcQty(e.target.value)} /> -
-