From 2350f744e33d0e53198c07c904cbe49e4f9762ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 6 Jul 2026 23:52:26 -0400 Subject: [PATCH] Audit cleanup: shared tool-arg validation, no dead exports, 500-line ceiling Deliverable and document tools move to agent-deliverable-tools, the three argument validators deduplicate into agent-tool-args (they had grown three copies across tool modules), and chat-helpers drops isRootMessage and countMessagesIn, which lost their last callers in the conversation-list rework. agent-tools lands at 404 lines; every source file is back under the ceiling. --- .../main/services/agent-connector-tools.ts | 6 +- .../main/services/agent-deliverable-tools.ts | 83 ++++++++++++++++ .../src/main/services/agent-read-messages.ts | 5 +- .../src/main/services/agent-tool-args.ts | 16 ++++ .../src/main/services/agent-tools.ts | 94 +------------------ .../renderer/src/pages/chat/chat-helpers.ts | 12 --- 6 files changed, 103 insertions(+), 113 deletions(-) create mode 100644 AgentCompany-New/src/main/services/agent-deliverable-tools.ts create mode 100644 AgentCompany-New/src/main/services/agent-tool-args.ts diff --git a/AgentCompany-New/src/main/services/agent-connector-tools.ts b/AgentCompany-New/src/main/services/agent-connector-tools.ts index 461b17a7..64259b3f 100644 --- a/AgentCompany-New/src/main/services/agent-connector-tools.ts +++ b/AgentCompany-New/src/main/services/agent-connector-tools.ts @@ -1,3 +1,4 @@ +import { requireString } from "./agent-tool-args"; import type { AgentToolContext, AgentToolDeps } from "./agent-tools"; import { ConnectorGrantsService } from "./composio/connector-grants"; import type { JsonRecord } from "./provider-http"; @@ -51,8 +52,3 @@ export function grantConnectorTool(deps: AgentToolDeps, ctx: AgentToolContext, a grantsService.revoke({ orgId: ctx.orgId, agentId: target.id, toolkitSlug: toolkit }); return `${target.name} no longer has access to the ${toolkit.toLowerCase()} connector.`; } - -function requireString(value: unknown, field: string): string { - if (typeof value !== "string" || !value.trim()) throw new Error(`"${field}" is required.`); - return value.trim(); -} diff --git a/AgentCompany-New/src/main/services/agent-deliverable-tools.ts b/AgentCompany-New/src/main/services/agent-deliverable-tools.ts new file mode 100644 index 00000000..0300783f --- /dev/null +++ b/AgentCompany-New/src/main/services/agent-deliverable-tools.ts @@ -0,0 +1,83 @@ +import { RISK_LEVEL } from "../../shared/domain"; +import { ApprovalService } from "./approval-service"; +import { writeDeliverable } from "./deliverable-writer"; +import { DocumentService } from "./document-service"; +import { recordWorkspaceFile } from "./files/file-object-store"; +import { requireString } from "./agent-tool-args"; +import type { AgentToolContext, AgentToolDeps } from "./agent-tools"; +import type { JsonRecord } from "./provider-http"; + +export function writeDeliverableTool(deps: AgentToolDeps, ctx: AgentToolContext, args: JsonRecord): string { + if (!ctx.workspace) throw new Error("No workspace is bound to this task; cannot write deliverables."); + const path = requireString(args.path, "path"); + const content = requireString(args.content, "content"); + const written = writeDeliverable({ workspaceRoot: ctx.workspace.localPath, relativePath: path, content }); + const fileId = recordWorkspaceFile(deps.db, { + orgId: ctx.orgId, + ownerType: "agent", + ownerId: ctx.agent.id, + workspaceRoot: ctx.workspace.localPath, + relativePath: written, + }); + const review = requestDeliverableReview(deps, ctx, { + title: `Review deliverable: ${written}`, + details: `Deliverable ${written} (${content.length} chars) produced by an agent run.`, + sourceEntityType: "file", + sourceEntityId: fileId, + }); + return `Wrote deliverable ${written} (file_id=${fileId}, chars=${content.length}).${review ? " It is queued for board review in Approvals." : ""}`; +} + +export function createDocumentTool(deps: AgentToolDeps, ctx: AgentToolContext, args: JsonRecord): string { + const title = requireString(args.title, "title"); + const docType = requireString(args.doc_type, "doc_type"); + const content = requireString(args.content, "content"); + const documentService = new DocumentService(deps.db); + const docId = documentService.create({ + orgId: ctx.orgId, + title, + docType, + content, + authorAgentId: ctx.agent.id, + reviewerAgentId: null, + projectId: ctx.taskProjectId, + goalId: ctx.taskGoalId, + taskId: null, + }); + const review = requestDeliverableReview(deps, ctx, { + title: `Review ${docType}: ${title}`, + details: `Document "${title}" (${docType}, ${content.length} chars) authored by an agent run.`, + sourceEntityType: "document", + sourceEntityId: docId, + }); + return `Created document "${title}" (doc_id=${docId}, type=${docType}, chars=${content.length}).${review ? " It is queued for board review in Approvals." : ""}`; +} + +/** + * In AI Co-Founder mode the operator reviews what the company produces, so + * every deliverable and document lands in the Approvals queue. AI CEO mode + * ships without review — that autonomy is the mode's contract. + */ +function requestDeliverableReview( + deps: AgentToolDeps, + ctx: AgentToolContext, + input: { title: string; details: string; sourceEntityType: "file" | "document"; sourceEntityId: string }, +): boolean { + const org = deps.db.prepare("select review_deliverables from organizations where id = ?") + .get(ctx.orgId) as { review_deliverables: number } | undefined; + if (!org) throw new Error("Organization was not found."); + if (org.review_deliverables !== 1) return false; + new ApprovalService(deps.db).create( + { + orgId: ctx.orgId, + kind: "deliverable_review", + title: input.title, + details: input.details, + risk: RISK_LEVEL.LOW, + requestedByType: "agent", + requestedById: ctx.agent.id, + }, + { sourceEntityType: input.sourceEntityType, sourceEntityId: input.sourceEntityId }, + ); + return true; +} diff --git a/AgentCompany-New/src/main/services/agent-read-messages.ts b/AgentCompany-New/src/main/services/agent-read-messages.ts index f6d7d385..731a5f27 100644 --- a/AgentCompany-New/src/main/services/agent-read-messages.ts +++ b/AgentCompany-New/src/main/services/agent-read-messages.ts @@ -1,3 +1,4 @@ +import { optionalString } from "./agent-tool-args"; import type { AgentToolContext, AgentToolDeps } from "./agent-tools"; import type { JsonRecord } from "./provider-http"; @@ -71,7 +72,3 @@ function recentMessagesIn( `).all(spaceId, orgId, limit) as Array<{ body_md: string; sender_name: string; thread_id: string }>; return rows.reverse(); } - -function optionalString(value: unknown): string | null { - return typeof value === "string" && value.trim() ? value.trim() : null; -} diff --git a/AgentCompany-New/src/main/services/agent-tool-args.ts b/AgentCompany-New/src/main/services/agent-tool-args.ts new file mode 100644 index 00000000..8d94ab4a --- /dev/null +++ b/AgentCompany-New/src/main/services/agent-tool-args.ts @@ -0,0 +1,16 @@ +/** Shared argument validation for agent tool implementations. */ + +export function requireString(value: unknown, field: string): string { + if (typeof value !== "string" || !value.trim()) throw new Error(`"${field}" is required.`); + return value.trim(); +} + +export function optionalString(value: unknown): string | null { + return typeof value === "string" && value.trim() ? value.trim() : null; +} + +export function optionalStringArray(value: unknown, field: string): string[] { + if (value === undefined) return []; + if (!Array.isArray(value)) throw new Error(`"${field}" must be an array.`); + return value.map((item, index) => requireString(item, `${field}[${index}]`)); +} diff --git a/AgentCompany-New/src/main/services/agent-tools.ts b/AgentCompany-New/src/main/services/agent-tools.ts index fb2653f7..fade945d 100644 --- a/AgentCompany-New/src/main/services/agent-tools.ts +++ b/AgentCompany-New/src/main/services/agent-tools.ts @@ -3,14 +3,12 @@ import { RISK_LEVEL, TASK_PRIORITY, TASK_STATUS, type RiskLevel } from "../../sh import { ORGANIZATION_DEPARTMENTS } from "./org-governance-service"; import type { OrganizationService } from "./organization-service"; import type { ChatService } from "./chat-service"; -import { ApprovalService } from "./approval-service"; -import { writeDeliverable } from "./deliverable-writer"; -import { DocumentService } from "./document-service"; -import { recordWorkspaceFile } from "./files/file-object-store"; import { canonicalMentionHandle } from "../../shared/mention-handles"; import { readMessagesTool } from "./agent-read-messages"; import { grantConnectorTool, listConnectorsTool } from "./agent-connector-tools"; +import { createDocumentTool, writeDeliverableTool } from "./agent-deliverable-tools"; import { describeSend } from "./agent-message-presence"; +import { optionalString, optionalStringArray, requireString } from "./agent-tool-args"; import type { JsonRecord } from "./provider-http"; import type { ToolCall, ToolDefinition } from "./provider-tool-turn"; @@ -270,55 +268,6 @@ function updateTaskStatus(deps: AgentToolDeps, ctx: AgentToolContext, args: Json return `Task ${taskId} moved to ${status}.`; } -function writeDeliverableTool(deps: AgentToolDeps, ctx: AgentToolContext, args: JsonRecord): string { - if (!ctx.workspace) throw new Error("No workspace is bound to this task; cannot write deliverables."); - const path = requireString(args.path, "path"); - const content = requireString(args.content, "content"); - const written = writeDeliverable({ workspaceRoot: ctx.workspace.localPath, relativePath: path, content }); - const fileId = recordWorkspaceFile(deps.db, { - orgId: ctx.orgId, - ownerType: "agent", - ownerId: ctx.agent.id, - workspaceRoot: ctx.workspace.localPath, - relativePath: written, - }); - const review = requestDeliverableReview(deps, ctx, { - title: `Review deliverable: ${written}`, - details: `Deliverable ${written} (${content.length} chars) produced by an agent run.`, - sourceEntityType: "file", - sourceEntityId: fileId, - }); - return `Wrote deliverable ${written} (file_id=${fileId}, chars=${content.length}).${review ? " It is queued for board review in Approvals." : ""}`; -} - -/** - * In AI Co-Founder mode the operator reviews what the company produces, so - * every deliverable and document lands in the Approvals queue. AI CEO mode - * ships without review — that autonomy is the mode's contract. - */ -function requestDeliverableReview( - deps: AgentToolDeps, - ctx: AgentToolContext, - input: { title: string; details: string; sourceEntityType: "file" | "document"; sourceEntityId: string }, -): boolean { - const org = deps.db.prepare("select review_deliverables from organizations where id = ?") - .get(ctx.orgId) as { review_deliverables: number } | undefined; - if (!org) throw new Error("Organization was not found."); - if (org.review_deliverables !== 1) return false; - new ApprovalService(deps.db).create( - { - orgId: ctx.orgId, - kind: "deliverable_review", - title: input.title, - details: input.details, - risk: RISK_LEVEL.LOW, - requestedByType: "agent", - requestedById: ctx.agent.id, - }, - { sourceEntityType: input.sourceEntityType, sourceEntityId: input.sourceEntityId }, - ); - return true; -} function sendMessageTool(deps: AgentToolDeps, ctx: AgentToolContext, args: JsonRecord): string { const channel = optionalString(args.channel); @@ -404,30 +353,6 @@ function resolveTeammateByName( throw new Error(`No teammate named "${name}".`); } -function createDocumentTool(deps: AgentToolDeps, ctx: AgentToolContext, args: JsonRecord): string { - const title = requireString(args.title, "title"); - const docType = requireString(args.doc_type, "doc_type"); - const content = requireString(args.content, "content"); - const documentService = new DocumentService(deps.db); - const docId = documentService.create({ - orgId: ctx.orgId, - title, - docType, - content, - authorAgentId: ctx.agent.id, - reviewerAgentId: null, - projectId: ctx.taskProjectId, - goalId: ctx.taskGoalId, - taskId: null, - }); - const review = requestDeliverableReview(deps, ctx, { - title: `Review ${docType}: ${title}`, - details: `Document "${title}" (${docType}, ${content.length} chars) authored by an agent run.`, - sourceEntityType: "document", - sourceEntityId: docId, - }); - return `Created document "${title}" (doc_id=${docId}, type=${docType}, chars=${content.length}).${review ? " It is queued for board review in Approvals." : ""}`; -} interface RunnableAssignmentAgent { id: string; @@ -491,21 +416,6 @@ function withSavepoint(db: DatabaseSync, name: string, operation: () => T): T } } -function requireString(value: unknown, field: string): string { - if (typeof value !== "string" || !value.trim()) throw new Error(`"${field}" is required.`); - return value.trim(); -} - -function optionalString(value: unknown): string | null { - return typeof value === "string" && value.trim() ? value.trim() : null; -} - -function optionalStringArray(value: unknown, field: string): string[] { - if (value === undefined) return []; - if (!Array.isArray(value)) throw new Error(`"${field}" must be an array.`); - return value.map((item, index) => requireString(item, `${field}[${index}]`)); -} - function normalizeRisk(value: unknown): RiskLevel { if (value === RISK_LEVEL.MEDIUM) return RISK_LEVEL.MEDIUM; if (value === RISK_LEVEL.HIGH) return RISK_LEVEL.HIGH; diff --git a/AgentCompany-New/src/renderer/src/pages/chat/chat-helpers.ts b/AgentCompany-New/src/renderer/src/pages/chat/chat-helpers.ts index 29824119..da3eb818 100644 --- a/AgentCompany-New/src/renderer/src/pages/chat/chat-helpers.ts +++ b/AgentCompany-New/src/renderer/src/pages/chat/chat-helpers.ts @@ -6,7 +6,6 @@ import type { ChatMessageRecord, ChatReadStateRecord, ChatSpaceRecord, - ChatThreadRecord, EmployeeRecord, FileObjectRecord, } from "../../../../shared/domain"; @@ -31,13 +30,6 @@ export function senderName(message: ChatMessageRecord, context: MessageContext): return "System"; } -export function isRootMessage(message: ChatMessageRecord, threads: ChatThreadRecord[]): boolean { - if (!message.threadId) return true; - const thread = threads.find((item) => item.id === message.threadId); - if (!thread) return true; - return thread.rootMessageId === message.id; -} - export function messageAttachments( messageId: string, attachments: ChatAttachmentRecord[], @@ -185,10 +177,6 @@ function previewText(message: ChatMessageRecord): string { return firstLine.trim(); } -export function countMessagesIn(spaceId: string, messages: ChatMessageRecord[]): number { - return messages.filter((message) => message.spaceId === spaceId).length; -} - export function formatDay(iso: string): string { const date = new Date(iso); if (Number.isNaN(date.getTime())) return "—";