refactor: dedupe heartbeat-title check; JOIN thread visibility; typed skill service guard (P2-43, P2-44, P2-45)

This commit is contained in:
ZacharyZhang-NY committed 2026-04-16 23:07:01 -04:00
1 parent 6d0d0dd4a0
commit c47050964a
4 files changed
+35 -22

No files matched your search

+2 -1
View File
@@ -3,6 +3,7 @@ import { taskInputSchema } from "@shared/contracts";
import { IMPORTANT_DOC_TYPES } from "@shared/document-categories";
import { canAssignTask, canUpdateTask } from "../agent-permissions";
import { inferTaskType } from "../connector-matching";
import { isHeartbeatTaskTitle } from "../orchestration/heartbeat-task";
import { validateTaskCreation } from "../task-quality-gates";
import { requireParam, type AgentApiServer } from "./server-core";
@@ -303,7 +304,7 @@ export function registerTaskRoutes(server: AgentApiServer) {
server.json(res, 403, { error: "Current agent is not allowed to assign this task to the selected agent" });
return;
}
const isHeartbeat = input.title.startsWith("[Heartbeat]");
const isHeartbeat = isHeartbeatTaskTitle(input.title);
const taskValidation = validateTaskCreation({ title: input.title, description: input.description ?? "" }, isHeartbeat);
if (!taskValidation.valid) {
server.json(res, 422, { error: taskValidation.errors.join("; ") });
+5 -3
View File
@@ -132,13 +132,15 @@ export function buildThreadMethods(getDb: () => DatabaseSync, readAll: ReadAll)
companyId: string,
options: { limit?: number; offset?: number } = {},
): ConversationEventRecord[] {
if (!getThread(threadId, companyId)) return [];
const limit = options.limit ?? 50;
const offset = options.offset ?? 0;
const rows = readAll<SqliteRow>(
`SELECT id, thread_id, actor_id, actor_type, event_type, body, metadata_json, created_at
FROM conversation_events WHERE thread_id = ? ORDER BY created_at ASC LIMIT ? OFFSET ?`,
threadId, limit, offset,
FROM conversation_events
WHERE thread_id = ?
AND thread_id IN (SELECT id FROM conversation_threads WHERE id = ? AND company_id = ?)
ORDER BY created_at ASC LIMIT ? OFFSET ?`,
threadId, threadId, companyId, limit, offset,
);
return rows.map((r) => ({
id: r.id as string,
+2 -1
View File
@@ -6,6 +6,7 @@ import {
taskInputSchema,
} from "@shared/contracts";
import { fail, ok, type RegisterCoreHandlersDependencies } from "./ipc-core-common";
import { isHeartbeatTaskTitle } from "./orchestration/heartbeat-task";
import { validateTaskCreation, checkCapabilityMatch } from "./task-quality-gates";
export function registerCoreTaskGraphHandlers({
@@ -100,7 +101,7 @@ export function registerCoreTaskGraphHandlers({
}
const isNew = !parsed.id;
if (isNew) {
const isHeartbeat = parsed.title.startsWith("[Heartbeat]");
const isHeartbeat = isHeartbeatTaskTitle(parsed.title);
const validation = validateTaskCreation(parsed, isHeartbeat);
if (!validation.valid) {
return fail("TASK_VALIDATION_FAILED", validation.errors.join("; "));
+26 -17
View File
@@ -29,11 +29,13 @@ export function registerSkillHandlers({
skillService,
db,
}: SkillHandlerDependencies) {
function requireSkillService() {
function requireSkillService():
| { service: NonNullable<typeof skillService>; error?: undefined }
| { service?: undefined; error: ReturnType<typeof fail> } {
if (!skillService) {
return fail("SERVICE_NOT_READY", "Skill service is not initialized.");
return { error: fail("SERVICE_NOT_READY", "Skill service is not initialized.") };
}
return skillService;
return { service: skillService };
}
// ---- Import skill from source (GitHub URL, local path, raw URL, skills.sh) ----
@@ -41,8 +43,9 @@ export function registerSkillHandlers({
const parsed = importSkillSchema.parse(payload);
const companyError = ensureCompanyExists(parsed.companyId);
if (companyError) return companyError;
const svc = requireSkillService();
if ("ok" in svc) return svc;
const svcResult = requireSkillService();
if (svcResult.error) return svcResult.error;
const svc = svcResult.service;
const result = await svc.importFromSource(parsed.companyId, parsed.source);
if (result.ok) {
publishDomainChanged();
@@ -55,8 +58,9 @@ export function registerSkillHandlers({
const parsed = listSkillsSchema.parse(payload);
const companyError = ensureCompanyExists(parsed.companyId);
if (companyError) return companyError;
const svc = requireSkillService();
if ("ok" in svc) return svc;
const svcResult = requireSkillService();
if (svcResult.error) return svcResult.error;
const svc = svcResult.service;
// Scan workspace .agents/skills/ directories for new skills before listing
try {
await svc.scanWorkspaceSkills(parsed.companyId);
@@ -71,8 +75,9 @@ export function registerSkillHandlers({
const parsed = getSkillDetailSchema.parse(payload);
const companyError = ensureCompanyExists(parsed.companyId);
if (companyError) return companyError;
const svc = requireSkillService();
if ("ok" in svc) return svc;
const svcResult = requireSkillService();
if (svcResult.error) return svcResult.error;
const svc = svcResult.service;
return svc.getDetail(parsed.companyId, parsed.skillId);
});
@@ -81,8 +86,9 @@ export function registerSkillHandlers({
const parsed = deleteSkillSchema.parse(payload);
const companyError = ensureCompanyExists(parsed.companyId);
if (companyError) return companyError;
const svc = requireSkillService();
if ("ok" in svc) return svc;
const svcResult = requireSkillService();
if (svcResult.error) return svcResult.error;
const svc = svcResult.service;
const result = await svc.deleteSkill(parsed.companyId, parsed.skillId);
if (result.ok) {
publishDomainChanged();
@@ -95,8 +101,9 @@ export function registerSkillHandlers({
const parsed = assignSkillToAgentSchema.parse(payload);
const companyError = ensureCompanyExists(parsed.companyId);
if (companyError) return companyError;
const svc = requireSkillService();
if ("ok" in svc) return svc;
const svcResult = requireSkillService();
if (svcResult.error) return svcResult.error;
const svc = svcResult.service;
const result = svc.assignToAgent(parsed.companyId, parsed.agentId, parsed.skillId);
if (result.ok) {
publishDomainChanged();
@@ -109,8 +116,9 @@ export function registerSkillHandlers({
const parsed = removeSkillFromAgentSchema.parse(payload);
const companyError = ensureCompanyExists(parsed.companyId);
if (companyError) return companyError;
const svc = requireSkillService();
if ("ok" in svc) return svc;
const svcResult = requireSkillService();
if (svcResult.error) return svcResult.error;
const svc = svcResult.service;
const result = svc.removeFromAgent(parsed.companyId, parsed.agentId, parsed.skillId);
if (result.ok) {
publishDomainChanged();
@@ -123,8 +131,9 @@ export function registerSkillHandlers({
const parsed = checkSkillUpdateSchema.parse(payload);
const companyError = ensureCompanyExists(parsed.companyId);
if (companyError) return companyError;
const svc = requireSkillService();
if ("ok" in svc) return svc;
const svcResult = requireSkillService();
if (svcResult.error) return svcResult.error;
const svc = svcResult.service;
return svc.checkUpdateStatus(parsed.companyId, parsed.skillId);
});