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.
This commit is contained in:
1 parent
d64cf22feb
commit
2350f744e3
6 files changed
+103
-113
No files matched your search
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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}]`));
|
||||
}
|
||||
@@ -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<T>(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;
|
||||
|
||||
@@ -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 "—";
|
||||
|
||||
Reference in new issue
Block a user