diff --git a/AgentCompany-New/src/main/app/ipc-handlers.ts b/AgentCompany-New/src/main/app/ipc-handlers.ts index c0a6e38f..3589fe21 100644 --- a/AgentCompany-New/src/main/app/ipc-handlers.ts +++ b/AgentCompany-New/src/main/app/ipc-handlers.ts @@ -101,6 +101,9 @@ export function registerIpcHandlers(deps: IpcDeps): void { return fail("task_run_failed", error instanceof Error ? error.message : String(error)); } }); + ipcMain.handle(IPC_CHANNELS.createGoal, (_event, input) => mutate(() => organization.createGoal(input))); + ipcMain.handle(IPC_CHANNELS.createProject, (_event, input) => mutate(() => organization.createProject(input))); + ipcMain.handle(IPC_CHANNELS.createWorkspace, (_event, input) => mutate(() => organization.createWorkspace(input))); ipcMain.handle(IPC_CHANNELS.createApproval, (_event, input) => mutate(() => approvals.create(input))); ipcMain.handle(IPC_CHANNELS.decideApproval, (_event, input) => mutate(() => approvals.decide(input))); ipcMain.handle(IPC_CHANNELS.syncEmployeeIdentity, (_event, input) => mutate(() => identityAccess.syncEmployeeIdentity(input))); diff --git a/AgentCompany-New/src/main/services/organization-service.ts b/AgentCompany-New/src/main/services/organization-service.ts index 3be290f8..0833e7a7 100644 --- a/AgentCompany-New/src/main/services/organization-service.ts +++ b/AgentCompany-New/src/main/services/organization-service.ts @@ -2,7 +2,7 @@ import type { DatabaseSync } from "node:sqlite"; import { APPROVAL_STATE, RISK_LEVEL, TASK_STATUS, type ApprovalState } from "../../shared/domain"; import { PROVIDER_CATALOG } from "../../shared/provider-catalog"; import { createId } from "../../shared/ids"; -import type { CreateAgentInput, CreateOrganizationInput, CreateTaskInput, UpdateAgentInput } from "../../shared/commands"; +import type { CreateAgentInput, CreateGoalInput, CreateOrganizationInput, CreateProjectInput, CreateTaskInput, CreateWorkspaceInput, UpdateAgentInput } from "../../shared/commands"; import { AuditService } from "./audit-service"; import { PolicyService } from "./policy-service"; import { IdentityAccessService } from "./identity-access-service"; @@ -382,6 +382,67 @@ export class OrganizationService { } } + createGoal(input: CreateGoalInput): string { + const id = createId("gol"); + const now = new Date().toISOString(); + if (input.ownerAgentId) this.verifyOptionalOrgEntity("agents", input.ownerAgentId, input.orgId, "Owner agent"); + this.db.prepare(` + insert into goals(id, org_id, title, description, status, owner_agent_id, created_at, updated_at) + values (?, ?, ?, ?, ?, ?, ?, ?) + `).run(id, input.orgId, input.title.trim(), input.description.trim(), "active", input.ownerAgentId, now, now); + this.audit.record({ + orgId: input.orgId, + entityType: "goal", + entityId: id, + actorType: "employee", + actorId: null, + action: "created", + payload: { title: input.title.trim() }, + }); + return id; + } + + createProject(input: CreateProjectInput): string { + const id = createId("prj"); + const now = new Date().toISOString(); + if (input.goalId) this.verifyOptionalOrgEntity("goals", input.goalId, input.orgId, "Goal"); + if (input.leadAgentId) this.verifyOptionalOrgEntity("agents", input.leadAgentId, input.orgId, "Lead agent"); + this.db.prepare(` + insert into projects(id, org_id, goal_id, name, description, lead_agent_id, status, created_at, updated_at) + values (?, ?, ?, ?, ?, ?, ?, ?, ?) + `).run(id, input.orgId, input.goalId, input.name.trim(), input.description.trim(), input.leadAgentId, "in_progress", now, now); + this.audit.record({ + orgId: input.orgId, + entityType: "project", + entityId: id, + actorType: "employee", + actorId: null, + action: "created", + payload: { name: input.name.trim() }, + }); + return id; + } + + createWorkspace(input: CreateWorkspaceInput): string { + const id = createId("wsp"); + const now = new Date().toISOString(); + if (input.projectId) this.verifyProject(input.projectId, null, input.orgId); + this.db.prepare(` + insert into workspaces(id, org_id, name, local_path, repo_url, repo_ref, project_id, is_primary, created_at, updated_at) + values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `).run(id, input.orgId, input.name.trim(), input.localPath.trim(), "", "", input.projectId, 0, now, now); + this.audit.record({ + orgId: input.orgId, + entityType: "workspace", + entityId: id, + actorType: "employee", + actorId: null, + action: "created", + payload: { name: input.name.trim() }, + }); + return id; + } + updateTaskStatus(orgId: string, taskId: string, status: string): void { const result = this.db.prepare("update tasks set status = ?, updated_at = ? where id = ? and org_id = ?") .run(status, new Date().toISOString(), taskId, orgId); diff --git a/AgentCompany-New/src/preload/index.ts b/AgentCompany-New/src/preload/index.ts index e4b8172d..8f429192 100644 --- a/AgentCompany-New/src/preload/index.ts +++ b/AgentCompany-New/src/preload/index.ts @@ -17,6 +17,9 @@ const api: DesktopApi = { createTask: (input) => invoke(IPC_CHANNELS.createTask, input), updateTaskStatus: (input) => invoke(IPC_CHANNELS.updateTaskStatus, input), runTask: (input) => invoke(IPC_CHANNELS.runTask, input), + createGoal: (input) => invoke(IPC_CHANNELS.createGoal, input), + createProject: (input) => invoke(IPC_CHANNELS.createProject, input), + createWorkspace: (input) => invoke(IPC_CHANNELS.createWorkspace, input), createApproval: (input) => invoke(IPC_CHANNELS.createApproval, input), decideApproval: (input) => invoke(IPC_CHANNELS.decideApproval, input), syncEmployeeIdentity: (input) => invoke(IPC_CHANNELS.syncEmployeeIdentity, input), diff --git a/AgentCompany-New/src/renderer/src/pages/WorkPage.tsx b/AgentCompany-New/src/renderer/src/pages/WorkPage.tsx index 6a42e305..253e714a 100644 --- a/AgentCompany-New/src/renderer/src/pages/WorkPage.tsx +++ b/AgentCompany-New/src/renderer/src/pages/WorkPage.tsx @@ -16,6 +16,8 @@ import { type WorkspaceRecord, } from "../../../shared/domain"; import { NewTaskDialog } from "./NewTaskDialog"; +import { GoalDialog } from "./work/GoalDialog"; +import { ProjectDialog } from "./work/ProjectDialog"; import { TaskDetailDialog } from "./work/TaskDetailDialog"; import { ProjectOverview } from "./work/ProjectOverview"; import { canRun, latestTaskRun, nextStatus, prettyRunState, prettyStatus, providerFor, runTone } from "./work/task-helpers"; @@ -41,6 +43,8 @@ export function WorkPage({ snapshot, actions }: PageProps) { const orgId = activeOrgId(snapshot); const [view, setView] = useState("board"); const [newTaskOpen, setNewTaskOpen] = useState(false); + const [newGoalOpen, setNewGoalOpen] = useState(false); + const [newProjectOpen, setNewProjectOpen] = useState(false); const [priorityFilter, setPriorityFilter] = useState("all"); const [assigneeFilter, setAssigneeFilter] = useState("all"); const [detailTaskId, setDetailTaskId] = useState(null); @@ -88,6 +92,8 @@ export function WorkPage({ snapshot, actions }: PageProps) { onPriority={setPriorityFilter} onAssignee={setAssigneeFilter} /> + +

Work

@@ -124,6 +130,8 @@ export function WorkPage({ snapshot, actions }: PageProps) { )} {view === "timeline" && } + setNewGoalOpen(false)} /> + setNewProjectOpen(false)} /> setNewTaskOpen(false)} /> void; +}) { + const submit = (event: FormEvent) => { + event.preventDefault(); + const form = new FormData(event.currentTarget); + void actions.run(() => window.agentCompany.createGoal({ + orgId, + title: text(form, "title"), + description: text(form, "description"), + ownerAgentId: optional(form, "ownerAgentId"), + })).then((result) => { if (result) onClose(); }); + }; + + return ( + + +