Add structured chat delivery records
This commit is contained in:
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"),
|
||||
|
||||
Reference in new issue
Block a user