Add structured chat delivery records

This commit is contained in:
ZacharyZhang-NY committed 2026-05-23 21:04:59 -04:00
1 parent a27658b56f
commit 18e0c6338e
10 files changed
+133 -8

No files matched your search

@@ -0,0 +1,21 @@
import type { ResolvedMention } from "./chat-mentions";
export interface ChatMessageStructureInput {
slash: { command: string; args: string } | null;
mentions: ResolvedMention[];
attachmentFileIds: string[];
}
export function buildChatMessageStructure(input: ChatMessageStructureInput): string {
return JSON.stringify({
format: "markdown",
slashInvocation: input.slash ? { command: input.slash.command, args: input.slash.args } : null,
mentions: input.mentions.map((mention) => ({
targetType: mention.target.type,
targetId: mention.target.id,
offsetStart: mention.offsetStart,
offsetEnd: mention.offsetEnd,
})),
attachmentFileIds: input.attachmentFileIds,
});
}
@@ -9,10 +9,12 @@ import { createId } from "../../shared/ids";
import { ApprovalService } from "./approval-service";
import { AuditService } from "./audit-service";
import { BoardInterventionService, parseBoardCommand } from "./board-intervention-service";
import { buildChatMessageStructure } from "./chat-message-structure";
import { recordChatMentions, resolveChatMentions, type MentionTarget, type ResolvedMention } from "./chat-mentions";
const SPACE_TYPES = new Set<CreateChatSpaceInput["spaceType"]>(["company", "department", "project", "direct"]);
const SPACE_VISIBILITIES = new Set<CreateChatSpaceInput["visibility"]>(["workspace", "restricted"]);
const RETENTION_POLICIES = new Set(["standard_365d", "restricted_365d"]);
interface AttachmentCandidate {
fileId: string;
@@ -71,9 +73,9 @@ export class ChatService {
const id = createId("spc");
const now = new Date().toISOString();
this.db.prepare(`
insert into chat_spaces(id, org_id, space_type, name, topic, visibility, created_at)
values (?, ?, ?, ?, ?, ?, ?)
`).run(id, input.orgId, input.spaceType, name, topic, input.visibility, now);
insert into chat_spaces(id, org_id, space_type, name, topic, visibility, retention_policy, created_at)
values (?, ?, ?, ?, ?, ?, ?, ?)
`).run(id, input.orgId, input.spaceType, name, topic, input.visibility, retentionPolicy(input.retentionPolicy, input.visibility), now);
const members = broadSpace ? this.resolveWorkspaceMembers(input.orgId) : explicitMembers;
for (const member of members) this.addMembership(id, member, input.visibility === "restricted" ? "approver" : "member");
@@ -101,16 +103,19 @@ export class ChatService {
const slash = parseSlashInvocation(body);
if (slash && !ALLOWED_SLASH_COMMANDS.has(slash.command)) throw new Error(`Slash command /${slash.command} is unavailable.`);
if (slash) validateSlashInvocation(slash);
const attachments = this.requireAttachments(input.orgId, input.attachmentFileIds ?? [], sender);
const attachmentFileIds = input.attachmentFileIds ?? [];
const attachments = this.requireAttachments(input.orgId, attachmentFileIds, sender);
const mentions = resolveChatMentions(this.db, input.orgId, input.spaceId, body);
const structuredBody = buildChatMessageStructure({ slash, mentions, attachmentFileIds });
const id = createId("msg");
const threadId = input.threadId ?? createId("thread");
const now = new Date().toISOString();
this.db.prepare(`
insert into chat_messages(
id, org_id, space_id, thread_id, sender_type, sender_id, body_md, created_at, edited_at, deleted_at
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
id, org_id, space_id, thread_id, sender_type, sender_id, body_md, body_structured_json,
delivery_state, created_at, edited_at, deleted_at
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).run(
id,
input.orgId,
@@ -119,6 +124,8 @@ export class ChatService {
input.senderType,
input.senderId,
body,
structuredBody,
"sent",
now,
null,
null,
@@ -479,3 +486,9 @@ function createThreadTitle(body: string): string {
const firstLine = body.split(/\r?\n/, 1)[0].trim();
return firstLine.length > 80 ? `${firstLine.slice(0, 77)}...` : firstLine;
}
function retentionPolicy(value: string | undefined, visibility: CreateChatSpaceInput["visibility"]): string {
const policy = value ?? (visibility === "restricted" ? "restricted_365d" : "standard_365d");
if (!RETENTION_POLICIES.has(policy)) throw new Error("Chat retention policy is invalid.");
return policy;
}
@@ -262,7 +262,7 @@ export class SdlcService {
{ key: "provider_adapter_coverage", passed: missing.length === 0, value: `${adapterKeys.length}/${catalogKeys.length}` },
{ key: "provider_rate_limit_registry", passed: rateLimitRegistry === 1, value: rateLimitRegistry },
{ key: "provider_runtime_contract", passed: runtimeContractReady, value: runtimeContractReady },
{ key: "schema_version", passed: schemaVersion >= 24, value: schemaVersion },
{ key: "schema_version", passed: schemaVersion >= 25, value: schemaVersion },
];
}
@@ -92,6 +92,7 @@ export class SnapshotService {
taskRuns: list(this.db, "task_runs", "created_at asc"),
approvals: list(this.db, "approvals", "created_at desc"),
chatSpaces: list(this.db, "chat_spaces", "created_at asc"),
chatMemberships: list(this.db, "chat_memberships", "space_id asc, principal_type asc, principal_id asc"),
chatMessages: list(this.db, "chat_messages", "created_at asc"),
chatThreads: list(this.db, "chat_threads", "updated_at desc"),
chatAttachments: list(this.db, "chat_attachments", "message_id asc"),