@okrlinkhub/agent-factory 0.2.12 → 0.2.14

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.
@@ -42,6 +42,20 @@ const bridgeRuntimeConfigValidator = v.object({
42
42
  serviceKey: v.union(v.null(), v.string()),
43
43
  serviceKeySecretRef: v.union(v.null(), v.string()),
44
44
  });
45
+ const messageRuntimeConfigValidator = v.object({
46
+ systemPrompt: v.optional(v.string()),
47
+ });
48
+ const globalSkillStatusValidator = v.union(v.literal("active"), v.literal("disabled"));
49
+ const globalSkillReleaseChannelValidator = v.union(v.literal("stable"), v.literal("canary"));
50
+ const globalSkillModuleFormatValidator = v.union(v.literal("esm"), v.literal("cjs"));
51
+ const globalSkillManifestItemValidator = v.object({
52
+ slug: v.string(),
53
+ version: v.string(),
54
+ moduleFormat: globalSkillModuleFormatValidator,
55
+ entryPoint: v.string(),
56
+ sourceJs: v.string(),
57
+ sha256: v.string(),
58
+ });
45
59
  const BRIDGE_SECRET_REFS = {
46
60
  serviceKey: "agent-bridge.serviceKey",
47
61
  baseUrl: "agent-bridge.baseUrl",
@@ -49,6 +63,10 @@ const BRIDGE_SECRET_REFS = {
49
63
  serviceId: "agent-bridge.serviceId",
50
64
  appKey: "agent-bridge.appKey",
51
65
  };
66
+ const RUNTIME_CONFIG_KEYS = {
67
+ provider: "provider",
68
+ message: "message",
69
+ };
52
70
  export const enqueueMessage = mutation({
53
71
  args: {
54
72
  conversationId: v.string(),
@@ -63,6 +81,10 @@ export const enqueueMessage = mutation({
63
81
  returns: v.id("messageQueue"),
64
82
  handler: async (ctx, args) => {
65
83
  const nowMs = args.nowMs ?? Date.now();
84
+ const messageRuntimeConfigRow = await ctx.db
85
+ .query("runtimeConfig")
86
+ .withIndex("by_key", (q) => q.eq("key", RUNTIME_CONFIG_KEYS.message))
87
+ .unique();
66
88
  const profile = await ctx.db
67
89
  .query("agentProfiles")
68
90
  .withIndex("by_agentKey", (q) => q.eq("agentKey", args.agentKey))
@@ -82,6 +104,7 @@ export const enqueueMessage = mutation({
82
104
  }
83
105
  const payload = {
84
106
  ...args.payload,
107
+ messageText: appendSystemPromptToMessage(args.payload.messageText, messageRuntimeConfigRow?.messageConfig?.systemPrompt),
85
108
  providerUserId: providerUserIdStr,
86
109
  metadata: {
87
110
  ...(args.payload.metadata ?? {}),
@@ -188,9 +211,9 @@ export const upsertAgentProfile = mutation({
188
211
  agentKey: v.string(),
189
212
  providerUserId: v.optional(v.string()),
190
213
  version: v.string(),
191
- soulMd: v.string(),
214
+ soulMd: v.optional(v.string()),
192
215
  clientMd: v.optional(v.string()),
193
- skills: v.array(v.string()),
216
+ skills: v.optional(v.array(v.string())),
194
217
  secretsRef: v.array(v.string()),
195
218
  bridgeConfig: v.optional(bridgeProfileConfigValidator),
196
219
  enabled: v.boolean(),
@@ -211,6 +234,74 @@ export const upsertAgentProfile = mutation({
211
234
  return existing._id;
212
235
  },
213
236
  });
237
+ export const clearDeprecatedAgentProfileFields = mutation({
238
+ args: {
239
+ dryRun: v.optional(v.boolean()),
240
+ },
241
+ returns: v.object({
242
+ dryRun: v.boolean(),
243
+ scanned: v.number(),
244
+ updated: v.number(),
245
+ unchanged: v.number(),
246
+ clearedProviderUserId: v.number(),
247
+ clearedSoulMd: v.number(),
248
+ clearedClientMd: v.number(),
249
+ clearedSkills: v.number(),
250
+ updatedAgentKeys: v.array(v.string()),
251
+ }),
252
+ handler: async (ctx, args) => {
253
+ const profiles = await ctx.db.query("agentProfiles").collect();
254
+ const dryRun = args.dryRun ?? false;
255
+ let updated = 0;
256
+ let clearedProviderUserId = 0;
257
+ let clearedSoulMd = 0;
258
+ let clearedClientMd = 0;
259
+ let clearedSkills = 0;
260
+ const updatedAgentKeys = [];
261
+ for (const profile of profiles) {
262
+ const patch = {};
263
+ let shouldPatch = false;
264
+ if (profile.providerUserId !== undefined) {
265
+ patch.providerUserId = undefined;
266
+ clearedProviderUserId += 1;
267
+ shouldPatch = true;
268
+ }
269
+ if (profile.soulMd !== undefined) {
270
+ patch.soulMd = undefined;
271
+ clearedSoulMd += 1;
272
+ shouldPatch = true;
273
+ }
274
+ if (profile.clientMd !== undefined) {
275
+ patch.clientMd = undefined;
276
+ clearedClientMd += 1;
277
+ shouldPatch = true;
278
+ }
279
+ if (profile.skills !== undefined) {
280
+ patch.skills = undefined;
281
+ clearedSkills += 1;
282
+ shouldPatch = true;
283
+ }
284
+ if (!shouldPatch)
285
+ continue;
286
+ updated += 1;
287
+ updatedAgentKeys.push(profile.agentKey);
288
+ if (!dryRun) {
289
+ await ctx.db.patch(profile._id, patch);
290
+ }
291
+ }
292
+ return {
293
+ dryRun,
294
+ scanned: profiles.length,
295
+ updated,
296
+ unchanged: profiles.length - updated,
297
+ clearedProviderUserId,
298
+ clearedSoulMd,
299
+ clearedClientMd,
300
+ clearedSkills,
301
+ updatedAgentKeys,
302
+ };
303
+ },
304
+ });
214
305
  export const importPlaintextSecret = mutation({
215
306
  args: {
216
307
  secretRef: v.string(),
@@ -295,9 +386,9 @@ export const getProviderRuntimeConfig = internalQuery({
295
386
  handler: async (ctx) => {
296
387
  const row = await ctx.db
297
388
  .query("runtimeConfig")
298
- .withIndex("by_key", (q) => q.eq("key", "provider"))
389
+ .withIndex("by_key", (q) => q.eq("key", RUNTIME_CONFIG_KEYS.provider))
299
390
  .unique();
300
- if (!row) {
391
+ if (!row?.providerConfig) {
301
392
  return null;
302
393
  }
303
394
  return row.providerConfig;
@@ -313,11 +404,11 @@ export const upsertProviderRuntimeConfig = internalMutation({
313
404
  const nowMs = args.nowMs ?? Date.now();
314
405
  const existing = await ctx.db
315
406
  .query("runtimeConfig")
316
- .withIndex("by_key", (q) => q.eq("key", "provider"))
407
+ .withIndex("by_key", (q) => q.eq("key", RUNTIME_CONFIG_KEYS.provider))
317
408
  .unique();
318
409
  if (!existing) {
319
410
  await ctx.db.insert("runtimeConfig", {
320
- key: "provider",
411
+ key: RUNTIME_CONFIG_KEYS.provider,
321
412
  providerConfig: args.providerConfig,
322
413
  updatedAt: nowMs,
323
414
  });
@@ -336,9 +427,9 @@ export const providerRuntimeConfig = query({
336
427
  handler: async (ctx) => {
337
428
  const row = await ctx.db
338
429
  .query("runtimeConfig")
339
- .withIndex("by_key", (q) => q.eq("key", "provider"))
430
+ .withIndex("by_key", (q) => q.eq("key", RUNTIME_CONFIG_KEYS.provider))
340
431
  .unique();
341
- if (!row) {
432
+ if (!row?.providerConfig) {
342
433
  return null;
343
434
  }
344
435
  return row.providerConfig;
@@ -354,11 +445,11 @@ export const setProviderRuntimeConfig = mutation({
354
445
  const nowMs = args.nowMs ?? Date.now();
355
446
  const existing = await ctx.db
356
447
  .query("runtimeConfig")
357
- .withIndex("by_key", (q) => q.eq("key", "provider"))
448
+ .withIndex("by_key", (q) => q.eq("key", RUNTIME_CONFIG_KEYS.provider))
358
449
  .unique();
359
450
  if (!existing) {
360
451
  await ctx.db.insert("runtimeConfig", {
361
- key: "provider",
452
+ key: RUNTIME_CONFIG_KEYS.provider,
362
453
  providerConfig: args.providerConfig,
363
454
  updatedAt: nowMs,
364
455
  });
@@ -371,6 +462,438 @@ export const setProviderRuntimeConfig = mutation({
371
462
  return null;
372
463
  },
373
464
  });
465
+ export const getMessageRuntimeConfig = internalQuery({
466
+ args: {},
467
+ returns: v.union(v.null(), messageRuntimeConfigValidator),
468
+ handler: async (ctx) => {
469
+ const row = await ctx.db
470
+ .query("runtimeConfig")
471
+ .withIndex("by_key", (q) => q.eq("key", RUNTIME_CONFIG_KEYS.message))
472
+ .unique();
473
+ if (!row?.messageConfig) {
474
+ return null;
475
+ }
476
+ return row.messageConfig;
477
+ },
478
+ });
479
+ export const upsertMessageRuntimeConfig = internalMutation({
480
+ args: {
481
+ messageConfig: messageRuntimeConfigValidator,
482
+ nowMs: v.optional(v.number()),
483
+ },
484
+ returns: v.null(),
485
+ handler: async (ctx, args) => {
486
+ const nowMs = args.nowMs ?? Date.now();
487
+ const normalizedMessageConfig = normalizeMessageRuntimeConfig(args.messageConfig);
488
+ const existing = await ctx.db
489
+ .query("runtimeConfig")
490
+ .withIndex("by_key", (q) => q.eq("key", RUNTIME_CONFIG_KEYS.message))
491
+ .unique();
492
+ if (normalizedMessageConfig === null) {
493
+ if (existing) {
494
+ await ctx.db.delete(existing._id);
495
+ }
496
+ return null;
497
+ }
498
+ if (!existing) {
499
+ await ctx.db.insert("runtimeConfig", {
500
+ key: RUNTIME_CONFIG_KEYS.message,
501
+ messageConfig: normalizedMessageConfig,
502
+ updatedAt: nowMs,
503
+ });
504
+ return null;
505
+ }
506
+ await ctx.db.patch(existing._id, {
507
+ messageConfig: normalizedMessageConfig,
508
+ updatedAt: nowMs,
509
+ });
510
+ return null;
511
+ },
512
+ });
513
+ export const messageRuntimeConfig = query({
514
+ args: {},
515
+ returns: v.union(v.null(), messageRuntimeConfigValidator),
516
+ handler: async (ctx) => {
517
+ const row = await ctx.db
518
+ .query("runtimeConfig")
519
+ .withIndex("by_key", (q) => q.eq("key", RUNTIME_CONFIG_KEYS.message))
520
+ .unique();
521
+ if (!row?.messageConfig) {
522
+ return null;
523
+ }
524
+ return row.messageConfig;
525
+ },
526
+ });
527
+ export const setMessageRuntimeConfig = mutation({
528
+ args: {
529
+ messageConfig: messageRuntimeConfigValidator,
530
+ nowMs: v.optional(v.number()),
531
+ },
532
+ returns: v.null(),
533
+ handler: async (ctx, args) => {
534
+ const nowMs = args.nowMs ?? Date.now();
535
+ const normalizedMessageConfig = normalizeMessageRuntimeConfig(args.messageConfig);
536
+ const existing = await ctx.db
537
+ .query("runtimeConfig")
538
+ .withIndex("by_key", (q) => q.eq("key", RUNTIME_CONFIG_KEYS.message))
539
+ .unique();
540
+ if (normalizedMessageConfig === null) {
541
+ if (existing) {
542
+ await ctx.db.delete(existing._id);
543
+ }
544
+ return null;
545
+ }
546
+ if (!existing) {
547
+ await ctx.db.insert("runtimeConfig", {
548
+ key: RUNTIME_CONFIG_KEYS.message,
549
+ messageConfig: normalizedMessageConfig,
550
+ updatedAt: nowMs,
551
+ });
552
+ return null;
553
+ }
554
+ await ctx.db.patch(existing._id, {
555
+ messageConfig: normalizedMessageConfig,
556
+ updatedAt: nowMs,
557
+ });
558
+ return null;
559
+ },
560
+ });
561
+ export const deployGlobalSkill = mutation({
562
+ args: {
563
+ slug: v.string(),
564
+ displayName: v.optional(v.string()),
565
+ description: v.optional(v.string()),
566
+ version: v.string(),
567
+ sourceJs: v.string(),
568
+ entryPoint: v.optional(v.string()),
569
+ moduleFormat: v.optional(globalSkillModuleFormatValidator),
570
+ releaseChannel: v.optional(globalSkillReleaseChannelValidator),
571
+ actor: v.optional(v.string()),
572
+ nowMs: v.optional(v.number()),
573
+ },
574
+ returns: v.object({
575
+ skillId: v.id("globalSkills"),
576
+ versionId: v.id("globalSkillVersions"),
577
+ releaseId: v.id("globalSkillReleases"),
578
+ slug: v.string(),
579
+ version: v.string(),
580
+ sha256: v.string(),
581
+ releaseChannel: globalSkillReleaseChannelValidator,
582
+ }),
583
+ handler: async (ctx, args) => {
584
+ const nowMs = args.nowMs ?? Date.now();
585
+ const slug = args.slug.trim().toLowerCase();
586
+ const version = args.version.trim();
587
+ const entryPoint = (args.entryPoint ?? "default").trim();
588
+ const releaseChannel = args.releaseChannel ?? "stable";
589
+ const moduleFormat = args.moduleFormat ?? "esm";
590
+ const actor = args.actor?.trim() || "system";
591
+ const sourceJs = args.sourceJs.trim();
592
+ if (!/^[a-z0-9][a-z0-9-_]{1,127}$/.test(slug)) {
593
+ throw new Error("Invalid skill slug. Use lowercase letters, numbers, '-' and '_'.");
594
+ }
595
+ if (!/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/.test(version)) {
596
+ throw new Error("Invalid skill version. Use semantic versioning format.");
597
+ }
598
+ if (sourceJs.length < 16) {
599
+ throw new Error("Skill source is too short.");
600
+ }
601
+ if (sourceJs.length > 200_000) {
602
+ throw new Error("Skill source too large (max 200KB).");
603
+ }
604
+ if (!entryPoint) {
605
+ throw new Error("entryPoint is required.");
606
+ }
607
+ const sha256 = await computeSha256Hex(sourceJs);
608
+ const existingSkill = await ctx.db
609
+ .query("globalSkills")
610
+ .withIndex("by_slug", (q) => q.eq("slug", slug))
611
+ .unique();
612
+ const skillId = existingSkill?._id ??
613
+ (await ctx.db.insert("globalSkills", {
614
+ slug,
615
+ displayName: args.displayName?.trim() || slug,
616
+ description: args.description?.trim(),
617
+ status: "active",
618
+ createdBy: actor,
619
+ updatedBy: actor,
620
+ createdAt: nowMs,
621
+ updatedAt: nowMs,
622
+ }));
623
+ if (existingSkill) {
624
+ await ctx.db.patch(skillId, {
625
+ displayName: args.displayName?.trim() || existingSkill.displayName,
626
+ description: args.description !== undefined ? args.description.trim() : existingSkill.description,
627
+ status: "active",
628
+ updatedBy: actor,
629
+ updatedAt: nowMs,
630
+ });
631
+ }
632
+ const existingVersion = await ctx.db
633
+ .query("globalSkillVersions")
634
+ .withIndex("by_skillId_and_version", (q) => q.eq("skillId", skillId).eq("version", version))
635
+ .unique();
636
+ let versionId = existingVersion?._id;
637
+ if (!existingVersion) {
638
+ versionId = await ctx.db.insert("globalSkillVersions", {
639
+ skillId,
640
+ version,
641
+ moduleFormat,
642
+ entryPoint,
643
+ sourceJs,
644
+ sha256,
645
+ createdBy: actor,
646
+ createdAt: nowMs,
647
+ });
648
+ }
649
+ else if (existingVersion.sha256 !== sha256) {
650
+ throw new Error(`Skill ${slug}@${version} already exists with a different source.`);
651
+ }
652
+ const activeReleases = await ctx.db
653
+ .query("globalSkillReleases")
654
+ .withIndex("by_skillId_and_releaseChannel_and_isActive", (q) => q.eq("skillId", skillId).eq("releaseChannel", releaseChannel).eq("isActive", true))
655
+ .collect();
656
+ for (const release of activeReleases) {
657
+ await ctx.db.patch(release._id, { isActive: false });
658
+ }
659
+ const releaseId = await ctx.db.insert("globalSkillReleases", {
660
+ skillId,
661
+ versionId: versionId,
662
+ releaseChannel,
663
+ isActive: true,
664
+ activatedBy: actor,
665
+ activatedAt: nowMs,
666
+ });
667
+ return {
668
+ skillId,
669
+ versionId: versionId,
670
+ releaseId,
671
+ slug,
672
+ version,
673
+ sha256,
674
+ releaseChannel,
675
+ };
676
+ },
677
+ });
678
+ export const listGlobalSkills = query({
679
+ args: {
680
+ releaseChannel: v.optional(globalSkillReleaseChannelValidator),
681
+ status: v.optional(globalSkillStatusValidator),
682
+ limit: v.optional(v.number()),
683
+ },
684
+ returns: v.array(v.object({
685
+ skillId: v.id("globalSkills"),
686
+ slug: v.string(),
687
+ displayName: v.string(),
688
+ description: v.optional(v.string()),
689
+ status: globalSkillStatusValidator,
690
+ updatedAt: v.number(),
691
+ activeRelease: v.union(v.null(), v.object({
692
+ releaseId: v.id("globalSkillReleases"),
693
+ versionId: v.id("globalSkillVersions"),
694
+ version: v.string(),
695
+ sha256: v.string(),
696
+ moduleFormat: globalSkillModuleFormatValidator,
697
+ entryPoint: v.string(),
698
+ releaseChannel: globalSkillReleaseChannelValidator,
699
+ activatedAt: v.number(),
700
+ })),
701
+ })),
702
+ handler: async (ctx, args) => {
703
+ const releaseChannel = args.releaseChannel ?? "stable";
704
+ const limit = Math.max(1, Math.min(args.limit ?? 200, 500));
705
+ const skills = args.status !== undefined
706
+ ? await ctx.db
707
+ .query("globalSkills")
708
+ .withIndex("by_status", (q) => q.eq("status", args.status))
709
+ .take(limit)
710
+ : await ctx.db.query("globalSkills").take(limit);
711
+ const sortedSkills = [...skills].sort((a, b) => a.slug.localeCompare(b.slug));
712
+ const out = [];
713
+ for (const skill of sortedSkills) {
714
+ const activeRelease = await ctx.db
715
+ .query("globalSkillReleases")
716
+ .withIndex("by_skillId_and_releaseChannel_and_isActive", (q) => q.eq("skillId", skill._id).eq("releaseChannel", releaseChannel).eq("isActive", true))
717
+ .first();
718
+ let activeReleaseRow = null;
719
+ if (activeRelease) {
720
+ const version = await ctx.db.get(activeRelease.versionId);
721
+ if (version) {
722
+ activeReleaseRow = {
723
+ releaseId: activeRelease._id,
724
+ versionId: version._id,
725
+ version: version.version,
726
+ sha256: version.sha256,
727
+ moduleFormat: version.moduleFormat,
728
+ entryPoint: version.entryPoint,
729
+ releaseChannel: activeRelease.releaseChannel,
730
+ activatedAt: activeRelease.activatedAt,
731
+ };
732
+ }
733
+ }
734
+ out.push({
735
+ skillId: skill._id,
736
+ slug: skill.slug,
737
+ displayName: skill.displayName,
738
+ description: skill.description,
739
+ status: skill.status,
740
+ updatedAt: skill.updatedAt,
741
+ activeRelease: activeReleaseRow,
742
+ });
743
+ }
744
+ return out;
745
+ },
746
+ });
747
+ export const getWorkerGlobalSkillsManifest = query({
748
+ args: {
749
+ workspaceId: v.optional(v.string()),
750
+ workerId: v.optional(v.string()),
751
+ releaseChannel: v.optional(globalSkillReleaseChannelValidator),
752
+ },
753
+ returns: v.object({
754
+ manifestVersion: v.string(),
755
+ generatedAt: v.number(),
756
+ releaseChannel: globalSkillReleaseChannelValidator,
757
+ workspaceId: v.string(),
758
+ skills: v.array(globalSkillManifestItemValidator),
759
+ }),
760
+ handler: async (ctx, args) => {
761
+ const nowMs = Date.now();
762
+ const releaseChannel = args.releaseChannel ?? "stable";
763
+ const activeSkills = await ctx.db
764
+ .query("globalSkills")
765
+ .withIndex("by_status", (q) => q.eq("status", "active"))
766
+ .collect();
767
+ const sortedSkills = [...activeSkills].sort((a, b) => a.slug.localeCompare(b.slug));
768
+ const manifestSkills = [];
769
+ for (const skill of sortedSkills) {
770
+ const activeRelease = await ctx.db
771
+ .query("globalSkillReleases")
772
+ .withIndex("by_skillId_and_releaseChannel_and_isActive", (q) => q.eq("skillId", skill._id).eq("releaseChannel", releaseChannel).eq("isActive", true))
773
+ .first();
774
+ if (!activeRelease)
775
+ continue;
776
+ const version = await ctx.db.get(activeRelease.versionId);
777
+ if (!version)
778
+ continue;
779
+ manifestSkills.push({
780
+ slug: skill.slug,
781
+ version: version.version,
782
+ moduleFormat: version.moduleFormat,
783
+ entryPoint: version.entryPoint,
784
+ sourceJs: version.sourceJs,
785
+ sha256: version.sha256,
786
+ });
787
+ }
788
+ manifestSkills.sort((a, b) => {
789
+ if (a.slug !== b.slug)
790
+ return a.slug.localeCompare(b.slug);
791
+ return a.version.localeCompare(b.version);
792
+ });
793
+ const fingerprintSeed = manifestSkills
794
+ .map((row) => `${row.slug}@${row.version}:${row.sha256}`)
795
+ .join("|");
796
+ const manifestVersion = await computeSha256Hex(fingerprintSeed || "empty");
797
+ return {
798
+ manifestVersion,
799
+ generatedAt: nowMs,
800
+ releaseChannel,
801
+ workspaceId: args.workspaceId ?? "default",
802
+ skills: manifestSkills,
803
+ };
804
+ },
805
+ });
806
+ export const setGlobalSkillStatus = mutation({
807
+ args: {
808
+ slug: v.string(),
809
+ status: globalSkillStatusValidator,
810
+ actor: v.optional(v.string()),
811
+ nowMs: v.optional(v.number()),
812
+ },
813
+ returns: v.object({
814
+ updated: v.boolean(),
815
+ slug: v.string(),
816
+ status: globalSkillStatusValidator,
817
+ }),
818
+ handler: async (ctx, args) => {
819
+ const slug = args.slug.trim().toLowerCase();
820
+ const nowMs = args.nowMs ?? Date.now();
821
+ const actor = args.actor?.trim() || "system";
822
+ const skill = await ctx.db
823
+ .query("globalSkills")
824
+ .withIndex("by_slug", (q) => q.eq("slug", slug))
825
+ .unique();
826
+ if (!skill) {
827
+ return { updated: false, slug, status: args.status };
828
+ }
829
+ await ctx.db.patch(skill._id, {
830
+ status: args.status,
831
+ updatedBy: actor,
832
+ updatedAt: nowMs,
833
+ });
834
+ return { updated: true, slug, status: args.status };
835
+ },
836
+ });
837
+ export const deleteGlobalSkill = mutation({
838
+ args: {
839
+ slug: v.string(),
840
+ },
841
+ returns: v.object({
842
+ deleted: v.boolean(),
843
+ slug: v.string(),
844
+ deletedVersions: v.number(),
845
+ deletedReleases: v.number(),
846
+ }),
847
+ handler: async (ctx, args) => {
848
+ const slug = args.slug.trim().toLowerCase();
849
+ const skill = await ctx.db
850
+ .query("globalSkills")
851
+ .withIndex("by_slug", (q) => q.eq("slug", slug))
852
+ .unique();
853
+ if (!skill) {
854
+ return { deleted: false, slug, deletedVersions: 0, deletedReleases: 0 };
855
+ }
856
+ const stableActiveReleases = await ctx.db
857
+ .query("globalSkillReleases")
858
+ .withIndex("by_skillId_and_releaseChannel_and_isActive", (q) => q.eq("skillId", skill._id).eq("releaseChannel", "stable").eq("isActive", true))
859
+ .collect();
860
+ const stableInactiveReleases = await ctx.db
861
+ .query("globalSkillReleases")
862
+ .withIndex("by_skillId_and_releaseChannel_and_isActive", (q) => q.eq("skillId", skill._id).eq("releaseChannel", "stable").eq("isActive", false))
863
+ .collect();
864
+ const canaryActiveReleases = await ctx.db
865
+ .query("globalSkillReleases")
866
+ .withIndex("by_skillId_and_releaseChannel_and_isActive", (q) => q.eq("skillId", skill._id).eq("releaseChannel", "canary").eq("isActive", true))
867
+ .collect();
868
+ const canaryInactiveReleases = await ctx.db
869
+ .query("globalSkillReleases")
870
+ .withIndex("by_skillId_and_releaseChannel_and_isActive", (q) => q.eq("skillId", skill._id).eq("releaseChannel", "canary").eq("isActive", false))
871
+ .collect();
872
+ const allReleases = [
873
+ ...stableActiveReleases,
874
+ ...stableInactiveReleases,
875
+ ...canaryActiveReleases,
876
+ ...canaryInactiveReleases,
877
+ ];
878
+ const versions = await ctx.db
879
+ .query("globalSkillVersions")
880
+ .withIndex("by_skillId_and_createdAt", (q) => q.eq("skillId", skill._id))
881
+ .collect();
882
+ for (const release of allReleases) {
883
+ await ctx.db.delete(release._id);
884
+ }
885
+ for (const version of versions) {
886
+ await ctx.db.delete(version._id);
887
+ }
888
+ await ctx.db.delete(skill._id);
889
+ return {
890
+ deleted: true,
891
+ slug,
892
+ deletedVersions: versions.length,
893
+ deletedReleases: allReleases.length,
894
+ };
895
+ },
896
+ });
374
897
  export const generateMediaUploadUrl = mutation({
375
898
  args: {},
376
899
  returns: v.object({
@@ -1334,6 +1857,31 @@ async function resolveBridgeRuntimeConfig(ctx, profile) {
1334
1857
  serviceKeySecretRef,
1335
1858
  };
1336
1859
  }
1860
+ function appendSystemPromptToMessage(messageText, systemPrompt) {
1861
+ const normalizedSystemPrompt = normalizeSystemPrompt(systemPrompt);
1862
+ if (normalizedSystemPrompt === null) {
1863
+ return messageText;
1864
+ }
1865
+ const normalizedMessageText = messageText.trimEnd();
1866
+ if (normalizedMessageText.length === 0) {
1867
+ return normalizedSystemPrompt;
1868
+ }
1869
+ return `${normalizedMessageText}\n\n${normalizedSystemPrompt}`;
1870
+ }
1871
+ function normalizeMessageRuntimeConfig(messageConfig) {
1872
+ const systemPrompt = normalizeSystemPrompt(messageConfig?.systemPrompt);
1873
+ if (systemPrompt === null) {
1874
+ return null;
1875
+ }
1876
+ return { systemPrompt };
1877
+ }
1878
+ function normalizeSystemPrompt(systemPrompt) {
1879
+ if (typeof systemPrompt !== "string") {
1880
+ return null;
1881
+ }
1882
+ const normalizedSystemPrompt = systemPrompt.trim();
1883
+ return normalizedSystemPrompt.length > 0 ? normalizedSystemPrompt : null;
1884
+ }
1337
1885
  function getBridgeSecretRefsForProfile(agentKey, bridgeConfig) {
1338
1886
  if (!bridgeConfig?.enabled) {
1339
1887
  return [];
@@ -1375,6 +1923,13 @@ function fingerprintConversationDelta(deltaContext) {
1375
1923
  }
1376
1924
  return `f${(hash >>> 0).toString(16)}`;
1377
1925
  }
1926
+ async function computeSha256Hex(input) {
1927
+ const bytes = new TextEncoder().encode(input);
1928
+ const digest = await crypto.subtle.digest("SHA-256", bytes);
1929
+ return Array.from(new Uint8Array(digest))
1930
+ .map((value) => value.toString(16).padStart(2, "0"))
1931
+ .join("");
1932
+ }
1378
1933
  function encryptSecretValue(plaintext) {
1379
1934
  const units = Array.from(plaintext);
1380
1935
  return units