@okrlinkhub/agent-factory 2.0.3 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +102 -0
  2. package/dist/client/index.d.ts +53 -21
  3. package/dist/client/index.d.ts.map +1 -1
  4. package/dist/client/index.js +74 -3
  5. package/dist/client/index.js.map +1 -1
  6. package/dist/component/_generated/api.d.ts +2 -0
  7. package/dist/component/_generated/api.d.ts.map +1 -1
  8. package/dist/component/_generated/api.js.map +1 -1
  9. package/dist/component/_generated/component.d.ts +119 -2
  10. package/dist/component/_generated/component.d.ts.map +1 -1
  11. package/dist/component/identity.d.ts +6 -6
  12. package/dist/component/lib.d.ts +2 -1
  13. package/dist/component/lib.d.ts.map +1 -1
  14. package/dist/component/lib.js +2 -1
  15. package/dist/component/lib.js.map +1 -1
  16. package/dist/component/messageTemplates.d.ts +37 -0
  17. package/dist/component/messageTemplates.d.ts.map +1 -0
  18. package/dist/component/messageTemplates.js +177 -0
  19. package/dist/component/messageTemplates.js.map +1 -0
  20. package/dist/component/pushing.d.ts +37 -37
  21. package/dist/component/queue.d.ts +113 -90
  22. package/dist/component/queue.d.ts.map +1 -1
  23. package/dist/component/queue.js +122 -47
  24. package/dist/component/queue.js.map +1 -1
  25. package/dist/component/scheduler.d.ts +23 -23
  26. package/dist/component/schema.d.ts +86 -44
  27. package/dist/component/schema.d.ts.map +1 -1
  28. package/dist/component/schema.js +19 -1
  29. package/dist/component/schema.js.map +1 -1
  30. package/package.json +1 -1
  31. package/src/client/index.ts +76 -3
  32. package/src/component/_generated/api.ts +2 -0
  33. package/src/component/_generated/component.ts +159 -2
  34. package/src/component/lib.test.ts +125 -0
  35. package/src/component/lib.ts +8 -0
  36. package/src/component/messageTemplates.ts +205 -0
  37. package/src/component/queue.ts +165 -49
  38. package/src/component/schema.ts +22 -1
@@ -0,0 +1,205 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query } from "./_generated/server.js";
3
+ import type { MutationCtx } from "./_generated/server.js";
4
+
5
+ const messageTemplateViewValidator = v.object({
6
+ _id: v.id("messageTemplates"),
7
+ templateKey: v.string(),
8
+ title: v.string(),
9
+ text: v.string(),
10
+ tags: v.array(v.string()),
11
+ usageCount: v.number(),
12
+ enabled: v.boolean(),
13
+ createdBy: v.string(),
14
+ updatedBy: v.string(),
15
+ createdAt: v.number(),
16
+ updatedAt: v.number(),
17
+ });
18
+
19
+ function normalizeTemplateKey(value: string) {
20
+ return value.trim().toLowerCase();
21
+ }
22
+
23
+ function buildTemplateKeyBase(title: string) {
24
+ const normalized = title
25
+ .trim()
26
+ .toLowerCase()
27
+ .replace(/[^a-z0-9]+/g, "-")
28
+ .replace(/^-+|-+$/g, "");
29
+ return normalized || "message-template";
30
+ }
31
+
32
+ function normalizeTitle(value: string) {
33
+ return value.trim();
34
+ }
35
+
36
+ function normalizeText(value: string) {
37
+ return value.trim();
38
+ }
39
+
40
+ function normalizeTags(tags: Array<string>) {
41
+ return Array.from(
42
+ new Set(
43
+ tags
44
+ .map((tag) => tag.trim().toLowerCase())
45
+ .filter((tag) => tag.length > 0),
46
+ ),
47
+ ).sort((left, right) => left.localeCompare(right));
48
+ }
49
+
50
+ function validateMessageTemplateFields(input: {
51
+ title?: string;
52
+ text?: string;
53
+ }) {
54
+ if (input.title !== undefined && input.title.length === 0) {
55
+ throw new Error("Template title is required");
56
+ }
57
+ if (input.text !== undefined && input.text.length === 0) {
58
+ throw new Error("Template text is required");
59
+ }
60
+ }
61
+
62
+ async function resolveUniqueTemplateKey(
63
+ ctx: MutationCtx,
64
+ title: string,
65
+ currentTemplateId?: string,
66
+ ) {
67
+ const baseKey = buildTemplateKeyBase(title);
68
+ let candidate = baseKey;
69
+ let suffix = 2;
70
+
71
+ while (true) {
72
+ const existing = await ctx.db
73
+ .query("messageTemplates")
74
+ .withIndex("by_templateKey", (q) => q.eq("templateKey", candidate))
75
+ .unique();
76
+ if (!existing || String(existing._id) === currentTemplateId) {
77
+ return candidate;
78
+ }
79
+ candidate = `${baseKey}-${suffix}`;
80
+ suffix += 1;
81
+ }
82
+ }
83
+
84
+ export const createMessageTemplate = mutation({
85
+ args: {
86
+ title: v.string(),
87
+ text: v.string(),
88
+ tags: v.array(v.string()),
89
+ enabled: v.optional(v.boolean()),
90
+ actorUserId: v.string(),
91
+ nowMs: v.optional(v.number()),
92
+ },
93
+ returns: v.id("messageTemplates"),
94
+ handler: async (ctx, args) => {
95
+ const title = normalizeTitle(args.title);
96
+ const text = normalizeText(args.text);
97
+ const tags = normalizeTags(args.tags);
98
+ validateMessageTemplateFields({ title, text });
99
+ const templateKey = normalizeTemplateKey(await resolveUniqueTemplateKey(ctx, title));
100
+
101
+ const nowMs = args.nowMs ?? Date.now();
102
+ return await ctx.db.insert("messageTemplates", {
103
+ templateKey,
104
+ title,
105
+ text,
106
+ tags,
107
+ usageCount: 0,
108
+ enabled: args.enabled ?? true,
109
+ createdBy: args.actorUserId,
110
+ updatedBy: args.actorUserId,
111
+ createdAt: nowMs,
112
+ updatedAt: nowMs,
113
+ });
114
+ },
115
+ });
116
+
117
+ export const updateMessageTemplate = mutation({
118
+ args: {
119
+ templateId: v.id("messageTemplates"),
120
+ title: v.optional(v.string()),
121
+ text: v.optional(v.string()),
122
+ tags: v.optional(v.array(v.string())),
123
+ enabled: v.optional(v.boolean()),
124
+ actorUserId: v.string(),
125
+ nowMs: v.optional(v.number()),
126
+ },
127
+ returns: v.boolean(),
128
+ handler: async (ctx, args) => {
129
+ const template = await ctx.db.get(args.templateId);
130
+ if (!template) return false;
131
+
132
+ const title = args.title !== undefined ? normalizeTitle(args.title) : template.title;
133
+ const text = args.text !== undefined ? normalizeText(args.text) : template.text;
134
+ const tags = args.tags !== undefined ? normalizeTags(args.tags) : template.tags;
135
+ validateMessageTemplateFields({ title, text });
136
+ const templateKey =
137
+ title !== template.title
138
+ ? normalizeTemplateKey(await resolveUniqueTemplateKey(ctx, title, String(template._id)))
139
+ : template.templateKey;
140
+
141
+ const nowMs = args.nowMs ?? Date.now();
142
+ await ctx.db.patch(template._id, {
143
+ templateKey,
144
+ title,
145
+ text,
146
+ tags,
147
+ enabled: args.enabled ?? template.enabled,
148
+ updatedBy: args.actorUserId,
149
+ updatedAt: nowMs,
150
+ });
151
+ return true;
152
+ },
153
+ });
154
+
155
+ export const deleteMessageTemplate = mutation({
156
+ args: {
157
+ templateId: v.id("messageTemplates"),
158
+ },
159
+ returns: v.boolean(),
160
+ handler: async (ctx, args) => {
161
+ const template = await ctx.db.get(args.templateId);
162
+ if (!template) return false;
163
+ await ctx.db.delete(template._id);
164
+ return true;
165
+ },
166
+ });
167
+
168
+ export const listMessageTemplatesByCompany = query({
169
+ args: {
170
+ includeDisabled: v.optional(v.boolean()),
171
+ limit: v.optional(v.number()),
172
+ },
173
+ returns: v.array(messageTemplateViewValidator),
174
+ handler: async (ctx, args) => {
175
+ const includeDisabled = args.includeDisabled ?? false;
176
+ const limit = Math.max(1, Math.min(args.limit ?? 100, 200));
177
+
178
+ const rows = includeDisabled
179
+ ? await ctx.db.query("messageTemplates").take(limit)
180
+ : await ctx.db
181
+ .query("messageTemplates")
182
+ .withIndex("by_enabled", (q) => q.eq("enabled", true))
183
+ .take(limit);
184
+
185
+ rows.sort((left, right) => {
186
+ if (right.usageCount !== left.usageCount) {
187
+ return right.usageCount - left.usageCount;
188
+ }
189
+ return right.updatedAt - left.updatedAt;
190
+ });
191
+ return rows.map((row) => ({
192
+ _id: row._id,
193
+ templateKey: row.templateKey,
194
+ title: row.title,
195
+ text: row.text,
196
+ tags: row.tags,
197
+ usageCount: row.usageCount,
198
+ enabled: row.enabled,
199
+ createdBy: row.createdBy,
200
+ updatedBy: row.updatedBy,
201
+ createdAt: row.createdAt,
202
+ updatedAt: row.updatedAt,
203
+ }));
204
+ },
205
+ });
@@ -208,7 +208,6 @@ const globalSkillManifestItemValidator = v.object({
208
208
  version: v.string(),
209
209
  moduleFormat: globalSkillModuleFormatValidator,
210
210
  entryPoint: v.string(),
211
- sourceJs: v.string(),
212
211
  sha256: v.string(),
213
212
  skillDirName: v.string(),
214
213
  files: v.array(globalSkillManifestFileValidator),
@@ -742,7 +741,7 @@ export const deployGlobalSkill = mutation({
742
741
  displayName: v.optional(v.string()),
743
742
  description: v.optional(v.string()),
744
743
  version: v.string(),
745
- sourceJs: v.string(),
744
+ files: v.array(globalSkillManifestFileValidator),
746
745
  entryPoint: v.optional(v.string()),
747
746
  moduleFormat: v.optional(globalSkillModuleFormatValidator),
748
747
  releaseChannel: v.optional(globalSkillReleaseChannelValidator),
@@ -766,7 +765,7 @@ export const deployGlobalSkill = mutation({
766
765
  const releaseChannel = args.releaseChannel ?? "stable";
767
766
  const moduleFormat = args.moduleFormat ?? "esm";
768
767
  const actor = args.actor?.trim() || "system";
769
- const sourceJs = args.sourceJs.trim();
768
+ const files = await normalizeGlobalSkillBundleFiles(args.files, moduleFormat);
770
769
 
771
770
  if (!/^[a-z0-9][a-z0-9-_]{1,127}$/.test(slug)) {
772
771
  throw new Error("Invalid skill slug. Use lowercase letters, numbers, '-' and '_'.");
@@ -774,17 +773,11 @@ export const deployGlobalSkill = mutation({
774
773
  if (!/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/.test(version)) {
775
774
  throw new Error("Invalid skill version. Use semantic versioning format.");
776
775
  }
777
- if (sourceJs.length < 16) {
778
- throw new Error("Skill source is too short.");
779
- }
780
- if (sourceJs.length > 200_000) {
781
- throw new Error("Skill source too large (max 200KB).");
782
- }
783
776
  if (!entryPoint) {
784
777
  throw new Error("entryPoint is required.");
785
778
  }
786
779
 
787
- const sha256 = await computeSha256Hex(sourceJs);
780
+ const sha256 = await computeGlobalSkillBundleSha256(files);
788
781
 
789
782
  const existingSkill = await ctx.db
790
783
  .query("globalSkills")
@@ -826,13 +819,17 @@ export const deployGlobalSkill = mutation({
826
819
  version,
827
820
  moduleFormat,
828
821
  entryPoint,
829
- sourceJs,
822
+ files,
830
823
  sha256,
831
824
  createdBy: actor,
832
825
  createdAt: nowMs,
833
826
  });
834
- } else if (existingVersion.sha256 !== sha256) {
835
- throw new Error(`Skill ${slug}@${version} already exists with a different source.`);
827
+ } else if (
828
+ existingVersion.sha256 !== sha256 ||
829
+ existingVersion.moduleFormat !== moduleFormat ||
830
+ existingVersion.entryPoint !== entryPoint
831
+ ) {
832
+ throw new Error(`Skill ${slug}@${version} already exists with a different bundle.`);
836
833
  }
837
834
 
838
835
  const activeReleases = await ctx.db
@@ -993,7 +990,6 @@ export const getWorkerGlobalSkillsManifest = query({
993
990
  version: string;
994
991
  moduleFormat: "esm" | "cjs";
995
992
  entryPoint: string;
996
- sourceJs: string;
997
993
  sha256: string;
998
994
  skillDirName: string;
999
995
  files: Array<{
@@ -1019,7 +1015,7 @@ export const getWorkerGlobalSkillsManifest = query({
1019
1015
  version: version.version,
1020
1016
  moduleFormat: version.moduleFormat,
1021
1017
  entryPoint: version.entryPoint,
1022
- sourceJs: version.sourceJs,
1018
+ files: version.files,
1023
1019
  sha256: version.sha256,
1024
1020
  });
1025
1021
  manifestSkills.push({
@@ -1027,7 +1023,6 @@ export const getWorkerGlobalSkillsManifest = query({
1027
1023
  version: version.version,
1028
1024
  moduleFormat: version.moduleFormat,
1029
1025
  entryPoint: version.entryPoint,
1030
- sourceJs: version.sourceJs,
1031
1026
  sha256: version.sha256,
1032
1027
  skillDirName: materializedSkill.skillDirName,
1033
1028
  files: materializedSkill.files,
@@ -1040,7 +1035,14 @@ export const getWorkerGlobalSkillsManifest = query({
1040
1035
  });
1041
1036
 
1042
1037
  const fingerprintSeed = manifestSkills
1043
- .map((row) => `${row.slug}@${row.version}:${row.sha256}`)
1038
+ .map(
1039
+ (row) =>
1040
+ `${row.slug}@${row.version}:${row.sha256}:${row.files
1041
+ .filter((file) => file.path !== ".af-global-skill.json")
1042
+ .map((file) => `${file.path}:${file.sha256}`)
1043
+ .sort()
1044
+ .join(",")}`,
1045
+ )
1044
1046
  .join("|");
1045
1047
  const manifestVersion = await computeSha256Hex(fingerprintSeed || "empty");
1046
1048
 
@@ -2011,6 +2013,67 @@ export const sendMessageToUserAgent = mutation({
2011
2013
  },
2012
2014
  });
2013
2015
 
2016
+ export const sendMessageTemplateToUserAgent = mutation({
2017
+ args: {
2018
+ consumerUserId: v.string(),
2019
+ agentKey: v.string(),
2020
+ templateId: v.id("messageTemplates"),
2021
+ metadata: v.optional(v.record(v.string(), v.string())),
2022
+ nowMs: v.optional(v.number()),
2023
+ providerConfig: v.optional(providerConfigValidator),
2024
+ },
2025
+ returns: v.object({
2026
+ messageId: v.id("messageQueue"),
2027
+ usageCount: v.number(),
2028
+ }),
2029
+ handler: async (ctx, args) => {
2030
+ const template = await ctx.db.get(args.templateId);
2031
+ if (!template || !template.enabled) {
2032
+ throw new Error("Message template not found");
2033
+ }
2034
+
2035
+ const nowMs = args.nowMs ?? Date.now();
2036
+ const target = await resolveConversationTargetForUserAgent(
2037
+ ctx,
2038
+ args.consumerUserId,
2039
+ args.agentKey,
2040
+ true,
2041
+ );
2042
+ const providerUserId =
2043
+ target.telegramUserId ?? target.telegramChatId ?? args.consumerUserId;
2044
+ const usageCount = template.usageCount + 1;
2045
+ const messageId = await enqueueMessageRecord(ctx, {
2046
+ conversationId: target.conversationId,
2047
+ agentKey: args.agentKey,
2048
+ payload: {
2049
+ provider: target.provider,
2050
+ providerUserId,
2051
+ messageText: template.text,
2052
+ metadata: {
2053
+ ...(args.metadata ?? {}),
2054
+ consumerUserId: args.consumerUserId,
2055
+ source: "message_template",
2056
+ templateId: String(template._id),
2057
+ templateKey: template.templateKey,
2058
+ ...(target.telegramChatId ? { telegramChatId: target.telegramChatId } : {}),
2059
+ ...(target.telegramUserId ? { telegramUserId: target.telegramUserId } : {}),
2060
+ },
2061
+ },
2062
+ scheduledFor: nowMs,
2063
+ providerConfig: args.providerConfig,
2064
+ });
2065
+
2066
+ await ctx.db.patch(template._id, {
2067
+ usageCount,
2068
+ });
2069
+
2070
+ return {
2071
+ messageId,
2072
+ usageCount,
2073
+ };
2074
+ },
2075
+ });
2076
+
2014
2077
  export const listSnapshotsForConversation = query({
2015
2078
  args: {
2016
2079
  conversationId: v.string(),
@@ -3414,7 +3477,11 @@ async function buildGlobalSkillMaterialization(skill: {
3414
3477
  version: string;
3415
3478
  moduleFormat: "esm" | "cjs";
3416
3479
  entryPoint: string;
3417
- sourceJs: string;
3480
+ files: Array<{
3481
+ path: string;
3482
+ content: string;
3483
+ sha256: string;
3484
+ }>;
3418
3485
  sha256: string;
3419
3486
  }): Promise<{
3420
3487
  skillDirName: string;
@@ -3425,26 +3492,11 @@ async function buildGlobalSkillMaterialization(skill: {
3425
3492
  }>;
3426
3493
  }> {
3427
3494
  const skillDirName = normalizeGlobalSkillDirName(skill.slug);
3428
- const scriptExt = skill.moduleFormat === "cjs" ? "cjs" : "mjs";
3429
- const scriptRelativePath = `scripts/index.${scriptExt}`;
3430
- const skillMd = [
3431
- "---",
3432
- `name: ${skill.slug}`,
3433
- `description: Global skill ${skill.slug}@${skill.version} provisioned by agent-factory.`,
3434
- "---",
3435
- "",
3436
- "# Global Skill",
3437
- "",
3438
- "This skill is generated automatically from agent-factory globalSkills.",
3439
- "",
3440
- `- slug: ${skill.slug}`,
3441
- `- version: ${skill.version}`,
3442
- `- entryPoint: ${skill.entryPoint}`,
3443
- `- moduleFormat: ${skill.moduleFormat}`,
3444
- "",
3445
- "Do not edit manually.",
3446
- "",
3447
- ].join("\n");
3495
+ const bundleFiles = await normalizeGlobalSkillBundleFiles(skill.files, skill.moduleFormat);
3496
+ const bundleSha256 = await computeGlobalSkillBundleSha256(bundleFiles);
3497
+ if (bundleSha256 !== skill.sha256) {
3498
+ throw new Error(`Global skill bundle checksum mismatch for ${skill.slug}@${skill.version}.`);
3499
+ }
3448
3500
  const markerJson = `${JSON.stringify(
3449
3501
  {
3450
3502
  slug: skill.slug,
@@ -3453,22 +3505,12 @@ async function buildGlobalSkillMaterialization(skill: {
3453
3505
  managedBy: "agent-factory",
3454
3506
  entryPoint: skill.entryPoint,
3455
3507
  moduleFormat: skill.moduleFormat,
3456
- generatedAt: Date.now(),
3457
3508
  },
3458
3509
  null,
3459
3510
  2,
3460
3511
  )}\n`;
3461
3512
  const files = [
3462
- {
3463
- path: "SKILL.md",
3464
- content: `${skillMd}\n`,
3465
- sha256: await computeSha256Hex(`${skillMd}\n`),
3466
- },
3467
- {
3468
- path: scriptRelativePath,
3469
- content: `${skill.sourceJs.trimEnd()}\n`,
3470
- sha256: await computeSha256Hex(`${skill.sourceJs.trimEnd()}\n`),
3471
- },
3513
+ ...bundleFiles,
3472
3514
  {
3473
3515
  path: ".af-global-skill.json",
3474
3516
  content: markerJson,
@@ -3478,11 +3520,85 @@ async function buildGlobalSkillMaterialization(skill: {
3478
3520
  return { skillDirName, files };
3479
3521
  }
3480
3522
 
3523
+ async function normalizeGlobalSkillBundleFiles(
3524
+ rawFiles: Array<{
3525
+ path: string;
3526
+ content: string;
3527
+ sha256: string;
3528
+ }>,
3529
+ moduleFormat: "esm" | "cjs",
3530
+ ): Promise<Array<{
3531
+ path: string;
3532
+ content: string;
3533
+ sha256: string;
3534
+ }>> {
3535
+ if (!Array.isArray(rawFiles) || rawFiles.length === 0) {
3536
+ throw new Error("Global skill bundle must contain at least one file.");
3537
+ }
3538
+
3539
+ const byPath = new Map<string, { path: string; content: string; sha256: string }>();
3540
+ let totalBytes = 0;
3541
+ for (const rawFile of rawFiles) {
3542
+ const path = normalizeRelativePath(rawFile.path);
3543
+ if (path === ".af-global-skill.json") {
3544
+ throw new Error("Bundle files must not include .af-global-skill.json.");
3545
+ }
3546
+ if (byPath.has(path)) {
3547
+ throw new Error(`Duplicate bundle file path: ${path}`);
3548
+ }
3549
+ const content = typeof rawFile.content === "string" ? rawFile.content : "";
3550
+ totalBytes += new TextEncoder().encode(content).length;
3551
+ if (totalBytes > 200_000) {
3552
+ throw new Error("Global skill bundle too large (max 200KB).");
3553
+ }
3554
+ const sha256 = typeof rawFile.sha256 === "string" ? rawFile.sha256.trim() : "";
3555
+ if (!sha256) {
3556
+ throw new Error(`Missing bundle checksum for ${path}.`);
3557
+ }
3558
+ const computedSha = await computeSha256Hex(content);
3559
+ if (computedSha !== sha256) {
3560
+ throw new Error(`Global skill checksum mismatch for ${path}.`);
3561
+ }
3562
+ byPath.set(path, { path, content, sha256 });
3563
+ }
3564
+
3565
+ const entryScriptPath = `scripts/index.${moduleFormat === "cjs" ? "cjs" : "mjs"}`;
3566
+ if (!byPath.has("SKILL.md")) {
3567
+ throw new Error("Global skill bundle must include SKILL.md.");
3568
+ }
3569
+ if (!byPath.has(entryScriptPath)) {
3570
+ throw new Error(`Global skill bundle must include ${entryScriptPath}.`);
3571
+ }
3572
+
3573
+ return [...byPath.values()].sort((left, right) => left.path.localeCompare(right.path));
3574
+ }
3575
+
3576
+ async function computeGlobalSkillBundleSha256(
3577
+ files: Array<{
3578
+ path: string;
3579
+ content: string;
3580
+ sha256: string;
3581
+ }>,
3582
+ ): Promise<string> {
3583
+ const fingerprint = files
3584
+ .map((file) => `${file.path}\n${file.sha256}\n${file.content}`)
3585
+ .join("\n---\n");
3586
+ return await computeSha256Hex(fingerprint);
3587
+ }
3588
+
3481
3589
  function normalizeGlobalSkillDirName(slug: string): string {
3482
3590
  const normalized = slug.trim().toLowerCase().replace(/[^a-z0-9-_]/g, "-");
3483
3591
  return normalized.length > 0 ? normalized : "unnamed-skill";
3484
3592
  }
3485
3593
 
3594
+ function normalizeRelativePath(value: string): string {
3595
+ const normalized = String(value || "").replaceAll("\\", "/").replace(/^\/+/, "");
3596
+ if (!normalized || normalized === "." || normalized.includes("../")) {
3597
+ throw new Error(`invalid_relative_path:${value}`);
3598
+ }
3599
+ return normalized;
3600
+ }
3601
+
3486
3602
  function getBridgeSecretRefsForProfile(
3487
3603
  agentKey: string,
3488
3604
  bridgeConfig:
@@ -315,7 +315,13 @@ export default defineSchema({
315
315
  version: v.string(),
316
316
  moduleFormat: v.union(v.literal("esm"), v.literal("cjs")),
317
317
  entryPoint: v.string(),
318
- sourceJs: v.string(),
318
+ files: v.array(
319
+ v.object({
320
+ path: v.string(),
321
+ content: v.string(),
322
+ sha256: v.string(),
323
+ }),
324
+ ),
319
325
  sha256: v.string(),
320
326
  createdBy: v.string(),
321
327
  createdAt: v.number(),
@@ -404,6 +410,21 @@ export default defineSchema({
404
410
  .index("by_companyId_and_templateKey", ["companyId", "templateKey"])
405
411
  .index("by_companyId_and_enabled", ["companyId", "enabled"]),
406
412
 
413
+ messageTemplates: defineTable({
414
+ templateKey: v.string(),
415
+ title: v.string(),
416
+ text: v.string(),
417
+ tags: v.array(v.string()),
418
+ usageCount: v.number(),
419
+ enabled: v.boolean(),
420
+ createdBy: v.string(),
421
+ updatedBy: v.string(),
422
+ createdAt: v.number(),
423
+ updatedAt: v.number(),
424
+ })
425
+ .index("by_templateKey", ["templateKey"])
426
+ .index("by_enabled", ["enabled"]),
427
+
407
428
  messagePushJobs: defineTable({
408
429
  companyId: v.string(),
409
430
  consumerUserId: v.string(),