@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.
- package/README.md +102 -0
- package/dist/client/index.d.ts +53 -21
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +74 -3
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +2 -0
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/api.js.map +1 -1
- package/dist/component/_generated/component.d.ts +119 -2
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/identity.d.ts +6 -6
- package/dist/component/lib.d.ts +2 -1
- package/dist/component/lib.d.ts.map +1 -1
- package/dist/component/lib.js +2 -1
- package/dist/component/lib.js.map +1 -1
- package/dist/component/messageTemplates.d.ts +37 -0
- package/dist/component/messageTemplates.d.ts.map +1 -0
- package/dist/component/messageTemplates.js +177 -0
- package/dist/component/messageTemplates.js.map +1 -0
- package/dist/component/pushing.d.ts +37 -37
- package/dist/component/queue.d.ts +113 -90
- package/dist/component/queue.d.ts.map +1 -1
- package/dist/component/queue.js +122 -47
- package/dist/component/queue.js.map +1 -1
- package/dist/component/scheduler.d.ts +23 -23
- package/dist/component/schema.d.ts +86 -44
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +19 -1
- package/dist/component/schema.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +76 -3
- package/src/component/_generated/api.ts +2 -0
- package/src/component/_generated/component.ts +159 -2
- package/src/component/lib.test.ts +125 -0
- package/src/component/lib.ts +8 -0
- package/src/component/messageTemplates.ts +205 -0
- package/src/component/queue.ts +165 -49
- 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
|
+
});
|
package/src/component/queue.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
822
|
+
files,
|
|
830
823
|
sha256,
|
|
831
824
|
createdBy: actor,
|
|
832
825
|
createdAt: nowMs,
|
|
833
826
|
});
|
|
834
|
-
} else if (
|
|
835
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
3429
|
-
const
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
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:
|
package/src/component/schema.ts
CHANGED
|
@@ -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
|
-
|
|
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(),
|