@semiont/make-meaning 0.2.30-build.59 → 0.2.30-build.61

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/dist/index.js CHANGED
@@ -6,11 +6,11 @@ var ResourceContext = class {
6
6
  /**
7
7
  * Get resource metadata from view storage
8
8
  */
9
- static async getResourceMetadata(resourceId, config) {
9
+ static async getResourceMetadata(resourceId2, config) {
10
10
  const basePath = config.services.filesystem.path;
11
11
  const projectRoot = config._metadata?.projectRoot;
12
12
  const viewStorage = new FilesystemViewStorage(basePath, projectRoot);
13
- const view = await viewStorage.get(resourceId);
13
+ const view = await viewStorage.get(resourceId2);
14
14
  if (!view) {
15
15
  return null;
16
16
  }
@@ -97,7 +97,7 @@ var AnnotationContext = class {
97
97
  * @returns Rich context for LLM processing
98
98
  * @throws Error if annotation or resource not found
99
99
  */
100
- static async buildLLMContext(annotationUri, resourceId, config, options = {}) {
100
+ static async buildLLMContext(annotationUri2, resourceId2, config, options = {}) {
101
101
  const {
102
102
  includeSourceContext = true,
103
103
  includeTargetContext = true,
@@ -106,16 +106,16 @@ var AnnotationContext = class {
106
106
  if (contextWindow < 100 || contextWindow > 5e3) {
107
107
  throw new Error("contextWindow must be between 100 and 5000");
108
108
  }
109
- console.log(`[AnnotationContext] buildLLMContext called with annotationUri=${annotationUri}, resourceId=${resourceId}`);
109
+ console.log(`[AnnotationContext] buildLLMContext called with annotationUri=${annotationUri2}, resourceId=${resourceId2}`);
110
110
  const basePath = config.services.filesystem.path;
111
111
  console.log(`[AnnotationContext] basePath=${basePath}`);
112
112
  const projectRoot = config._metadata?.projectRoot;
113
113
  const viewStorage = new FilesystemViewStorage2(basePath, projectRoot);
114
114
  const repStore = new FilesystemRepresentationStore2({ basePath }, projectRoot);
115
- console.log(`[AnnotationContext] Getting view for resourceId=${resourceId}`);
115
+ console.log(`[AnnotationContext] Getting view for resourceId=${resourceId2}`);
116
116
  let sourceView;
117
117
  try {
118
- sourceView = await viewStorage.get(resourceId);
118
+ sourceView = await viewStorage.get(resourceId2);
119
119
  console.log(`[AnnotationContext] Got view:`, !!sourceView);
120
120
  if (!sourceView) {
121
121
  throw new Error("Source resource not found");
@@ -124,19 +124,19 @@ var AnnotationContext = class {
124
124
  console.error(`[AnnotationContext] Error getting view:`, error);
125
125
  throw error;
126
126
  }
127
- console.log(`[AnnotationContext] Looking for annotation ${annotationUri} in resource ${resourceId}`);
127
+ console.log(`[AnnotationContext] Looking for annotation ${annotationUri2} in resource ${resourceId2}`);
128
128
  console.log(`[AnnotationContext] View has ${sourceView.annotations.annotations.length} annotations`);
129
129
  console.log(`[AnnotationContext] First 5 annotation IDs:`, sourceView.annotations.annotations.slice(0, 5).map((a) => a.id));
130
- const annotation = sourceView.annotations.annotations.find((a) => a.id === annotationUri);
130
+ const annotation = sourceView.annotations.annotations.find((a) => a.id === annotationUri2);
131
131
  console.log(`[AnnotationContext] Found annotation:`, !!annotation);
132
132
  if (!annotation) {
133
133
  throw new Error("Annotation not found in view");
134
134
  }
135
135
  const targetSource = getTargetSource(annotation.target);
136
136
  const targetResourceId = targetSource.split("/").pop();
137
- console.log(`[AnnotationContext] Target source: ${targetSource}, Expected resource ID: ${resourceId}, Extracted ID: ${targetResourceId}`);
138
- if (targetResourceId !== resourceId) {
139
- throw new Error(`Annotation target resource ID (${targetResourceId}) does not match expected resource ID (${resourceId})`);
137
+ console.log(`[AnnotationContext] Target source: ${targetSource}, Expected resource ID: ${resourceId2}, Extracted ID: ${targetResourceId}`);
138
+ if (targetResourceId !== resourceId2) {
139
+ throw new Error(`Annotation target resource ID (${targetResourceId}) does not match expected resource ID (${resourceId2})`);
140
140
  }
141
141
  const sourceDoc = sourceView.resource;
142
142
  const bodySource = getBodySource(annotation.body);
@@ -233,16 +233,16 @@ var AnnotationContext = class {
233
233
  * Get resource annotations from view storage (fast path)
234
234
  * Throws if view missing
235
235
  */
236
- static async getResourceAnnotations(resourceId, config) {
236
+ static async getResourceAnnotations(resourceId2, config) {
237
237
  if (!config.services?.filesystem?.path) {
238
238
  throw new Error("Filesystem path not found in configuration");
239
239
  }
240
240
  const basePath = config.services.filesystem.path;
241
241
  const projectRoot = config._metadata?.projectRoot;
242
242
  const viewStorage = new FilesystemViewStorage2(basePath, projectRoot);
243
- const view = await viewStorage.get(resourceId);
243
+ const view = await viewStorage.get(resourceId2);
244
244
  if (!view) {
245
- throw new Error(`Resource ${resourceId} not found in view storage`);
245
+ throw new Error(`Resource ${resourceId2} not found in view storage`);
246
246
  }
247
247
  return view.annotations;
248
248
  }
@@ -250,8 +250,8 @@ var AnnotationContext = class {
250
250
  * Get all annotations
251
251
  * @returns Array of all annotation objects
252
252
  */
253
- static async getAllAnnotations(resourceId, config) {
254
- const annotations = await this.getResourceAnnotations(resourceId, config);
253
+ static async getAllAnnotations(resourceId2, config) {
254
+ const annotations = await this.getResourceAnnotations(resourceId2, config);
255
255
  return await this.enrichResolvedReferences(annotations.annotations, config);
256
256
  }
257
257
  /**
@@ -328,8 +328,8 @@ var AnnotationContext = class {
328
328
  * Get resource stats (version info)
329
329
  * @returns Version and timestamp info for the annotations
330
330
  */
331
- static async getResourceStats(resourceId, config) {
332
- const annotations = await this.getResourceAnnotations(resourceId, config);
331
+ static async getResourceStats(resourceId2, config) {
332
+ const annotations = await this.getResourceAnnotations(resourceId2, config);
333
333
  return {
334
334
  resourceId: annotations.resourceId,
335
335
  version: annotations.version,
@@ -339,24 +339,24 @@ var AnnotationContext = class {
339
339
  /**
340
340
  * Check if resource exists in view storage
341
341
  */
342
- static async resourceExists(resourceId, config) {
342
+ static async resourceExists(resourceId2, config) {
343
343
  if (!config.services?.filesystem?.path) {
344
344
  throw new Error("Filesystem path not found in configuration");
345
345
  }
346
346
  const basePath = config.services.filesystem.path;
347
347
  const projectRoot = config._metadata?.projectRoot;
348
348
  const viewStorage = new FilesystemViewStorage2(basePath, projectRoot);
349
- return await viewStorage.exists(resourceId);
349
+ return await viewStorage.exists(resourceId2);
350
350
  }
351
351
  /**
352
352
  * Get a single annotation by ID
353
353
  * O(1) lookup using resource ID to access view storage
354
354
  */
355
- static async getAnnotation(annotationId, resourceId, config) {
356
- const annotations = await this.getResourceAnnotations(resourceId, config);
355
+ static async getAnnotation(annotationId2, resourceId2, config) {
356
+ const annotations = await this.getResourceAnnotations(resourceId2, config);
357
357
  return annotations.annotations.find((a) => {
358
358
  const shortId = a.id.split("/").pop();
359
- return shortId === annotationId;
359
+ return shortId === annotationId2;
360
360
  }) || null;
361
361
  }
362
362
  /**
@@ -373,11 +373,11 @@ var AnnotationContext = class {
373
373
  /**
374
374
  * Get annotation context (selected text with surrounding context)
375
375
  */
376
- static async getAnnotationContext(annotationId, resourceId, contextBefore, contextAfter, config) {
376
+ static async getAnnotationContext(annotationId2, resourceId2, contextBefore, contextAfter, config) {
377
377
  const basePath = config.services.filesystem.path;
378
378
  const projectRoot = config._metadata?.projectRoot;
379
379
  const repStore = new FilesystemRepresentationStore2({ basePath }, projectRoot);
380
- const annotation = await this.getAnnotation(annotationId, resourceId, config);
380
+ const annotation = await this.getAnnotation(annotationId2, resourceId2, config);
381
381
  if (!annotation) {
382
382
  throw new Error("Annotation not found");
383
383
  }
@@ -409,11 +409,11 @@ var AnnotationContext = class {
409
409
  /**
410
410
  * Generate AI summary of annotation in context
411
411
  */
412
- static async generateAnnotationSummary(annotationId, resourceId, config) {
412
+ static async generateAnnotationSummary(annotationId2, resourceId2, config) {
413
413
  const basePath = config.services.filesystem.path;
414
414
  const projectRoot = config._metadata?.projectRoot;
415
415
  const repStore = new FilesystemRepresentationStore2({ basePath }, projectRoot);
416
- const annotation = await this.getAnnotation(annotationId, resourceId, config);
416
+ const annotation = await this.getAnnotation(annotationId2, resourceId2, config);
417
417
  if (!annotation) {
418
418
  throw new Error("Annotation not found");
419
419
  }
@@ -499,10 +499,10 @@ var GraphContext = class {
499
499
  * Get all resources referencing this resource (backlinks)
500
500
  * Requires graph traversal - must use graph database
501
501
  */
502
- static async getBacklinks(resourceId, config) {
502
+ static async getBacklinks(resourceId2, config) {
503
503
  const graphDb = await getGraphDatabase(config);
504
- const resourceUri = resourceIdToURI(resourceId, config.services.backend.publicURL);
505
- return await graphDb.getResourceReferencedBy(resourceUri);
504
+ const resourceUri2 = resourceIdToURI(resourceId2, config.services.backend.publicURL);
505
+ return await graphDb.getResourceReferencedBy(resourceUri2);
506
506
  }
507
507
  /**
508
508
  * Find shortest path between two resources
@@ -516,9 +516,9 @@ var GraphContext = class {
516
516
  * Get resource connections (graph edges)
517
517
  * Requires graph traversal - must use graph database
518
518
  */
519
- static async getResourceConnections(resourceId, config) {
519
+ static async getResourceConnections(resourceId2, config) {
520
520
  const graphDb = await getGraphDatabase(config);
521
- return await graphDb.getResourceConnections(resourceId);
521
+ return await graphDb.getResourceConnections(resourceId2);
522
522
  }
523
523
  /**
524
524
  * Search resources by name (cross-resource query)
@@ -550,14 +550,14 @@ var AnnotationDetection = class {
550
550
  * @param density - Optional target number of comments per 2000 words
551
551
  * @returns Array of validated comment matches
552
552
  */
553
- static async detectComments(resourceId, config, instructions, tone, density) {
554
- const resource = await ResourceContext.getResourceMetadata(resourceId, config);
553
+ static async detectComments(resourceId2, config, instructions, tone, density) {
554
+ const resource = await ResourceContext.getResourceMetadata(resourceId2, config);
555
555
  if (!resource) {
556
- throw new Error(`Resource ${resourceId} not found`);
556
+ throw new Error(`Resource ${resourceId2} not found`);
557
557
  }
558
- const content = await this.loadResourceContent(resourceId, config);
558
+ const content = await this.loadResourceContent(resourceId2, config);
559
559
  if (!content) {
560
- throw new Error(`Could not load content for resource ${resourceId}`);
560
+ throw new Error(`Could not load content for resource ${resourceId2}`);
561
561
  }
562
562
  const prompt = MotivationPrompts.buildCommentPrompt(content, instructions, tone, density);
563
563
  const response = await generateText2(
@@ -579,14 +579,14 @@ var AnnotationDetection = class {
579
579
  * @param density - Optional target number of highlights per 2000 words
580
580
  * @returns Array of validated highlight matches
581
581
  */
582
- static async detectHighlights(resourceId, config, instructions, density) {
583
- const resource = await ResourceContext.getResourceMetadata(resourceId, config);
582
+ static async detectHighlights(resourceId2, config, instructions, density) {
583
+ const resource = await ResourceContext.getResourceMetadata(resourceId2, config);
584
584
  if (!resource) {
585
- throw new Error(`Resource ${resourceId} not found`);
585
+ throw new Error(`Resource ${resourceId2} not found`);
586
586
  }
587
- const content = await this.loadResourceContent(resourceId, config);
587
+ const content = await this.loadResourceContent(resourceId2, config);
588
588
  if (!content) {
589
- throw new Error(`Could not load content for resource ${resourceId}`);
589
+ throw new Error(`Could not load content for resource ${resourceId2}`);
590
590
  }
591
591
  const prompt = MotivationPrompts.buildHighlightPrompt(content, instructions, density);
592
592
  const response = await generateText2(
@@ -609,14 +609,14 @@ var AnnotationDetection = class {
609
609
  * @param density - Optional target number of assessments per 2000 words
610
610
  * @returns Array of validated assessment matches
611
611
  */
612
- static async detectAssessments(resourceId, config, instructions, tone, density) {
613
- const resource = await ResourceContext.getResourceMetadata(resourceId, config);
612
+ static async detectAssessments(resourceId2, config, instructions, tone, density) {
613
+ const resource = await ResourceContext.getResourceMetadata(resourceId2, config);
614
614
  if (!resource) {
615
- throw new Error(`Resource ${resourceId} not found`);
615
+ throw new Error(`Resource ${resourceId2} not found`);
616
616
  }
617
- const content = await this.loadResourceContent(resourceId, config);
617
+ const content = await this.loadResourceContent(resourceId2, config);
618
618
  if (!content) {
619
- throw new Error(`Could not load content for resource ${resourceId}`);
619
+ throw new Error(`Could not load content for resource ${resourceId2}`);
620
620
  }
621
621
  const prompt = MotivationPrompts.buildAssessmentPrompt(content, instructions, tone, density);
622
622
  const response = await generateText2(
@@ -638,7 +638,7 @@ var AnnotationDetection = class {
638
638
  * @param category - The specific category to detect
639
639
  * @returns Array of validated tag matches
640
640
  */
641
- static async detectTags(resourceId, config, schemaId, category) {
641
+ static async detectTags(resourceId2, config, schemaId, category) {
642
642
  const schema = getTagSchema(schemaId);
643
643
  if (!schema) {
644
644
  throw new Error(`Invalid tag schema: ${schemaId}`);
@@ -647,13 +647,13 @@ var AnnotationDetection = class {
647
647
  if (!categoryInfo) {
648
648
  throw new Error(`Invalid category "${category}" for schema ${schemaId}`);
649
649
  }
650
- const resource = await ResourceContext.getResourceMetadata(resourceId, config);
650
+ const resource = await ResourceContext.getResourceMetadata(resourceId2, config);
651
651
  if (!resource) {
652
- throw new Error(`Resource ${resourceId} not found`);
652
+ throw new Error(`Resource ${resourceId2} not found`);
653
653
  }
654
- const content = await this.loadResourceContent(resourceId, config);
654
+ const content = await this.loadResourceContent(resourceId2, config);
655
655
  if (!content) {
656
- throw new Error(`Could not load content for resource ${resourceId}`);
656
+ throw new Error(`Could not load content for resource ${resourceId2}`);
657
657
  }
658
658
  const prompt = MotivationPrompts.buildTagPrompt(
659
659
  content,
@@ -683,8 +683,8 @@ var AnnotationDetection = class {
683
683
  * @param config - Environment configuration
684
684
  * @returns Resource content as string, or null if not available
685
685
  */
686
- static async loadResourceContent(resourceId, config) {
687
- const resource = await ResourceContext.getResourceMetadata(resourceId, config);
686
+ static async loadResourceContent(resourceId2, config) {
687
+ const resource = await ResourceContext.getResourceMetadata(resourceId2, config);
688
688
  if (!resource) return null;
689
689
  const primaryRep = getPrimaryRepresentation3(resource);
690
690
  if (!primaryRep) return null;
@@ -701,15 +701,1295 @@ var AnnotationDetection = class {
701
701
  }
702
702
  };
703
703
 
704
+ // src/jobs/workers/comment-detection-worker.ts
705
+ import { JobWorker } from "@semiont/jobs";
706
+ import { generateAnnotationId } from "@semiont/event-sourcing";
707
+ import { resourceIdToURI as resourceIdToURI2 } from "@semiont/core";
708
+ import { userId } from "@semiont/core";
709
+ var CommentDetectionWorker = class extends JobWorker {
710
+ constructor(jobQueue, config, eventStore) {
711
+ super(jobQueue);
712
+ this.config = config;
713
+ this.eventStore = eventStore;
714
+ }
715
+ isFirstProgress = true;
716
+ getWorkerName() {
717
+ return "CommentDetectionWorker";
718
+ }
719
+ canProcessJob(job) {
720
+ return job.type === "comment-detection";
721
+ }
722
+ async executeJob(job) {
723
+ if (job.type !== "comment-detection") {
724
+ throw new Error(`Invalid job type: ${job.type}`);
725
+ }
726
+ this.isFirstProgress = true;
727
+ await this.processCommentDetectionJob(job);
728
+ }
729
+ /**
730
+ * Override updateJobProgress to emit events to Event Store
731
+ */
732
+ async updateJobProgress(job) {
733
+ await super.updateJobProgress(job);
734
+ if (job.type !== "comment-detection") return;
735
+ const cdJob = job;
736
+ if (!cdJob.progress) return;
737
+ const baseEvent = {
738
+ resourceId: cdJob.resourceId,
739
+ userId: cdJob.userId,
740
+ version: 1
741
+ };
742
+ const isComplete = cdJob.progress.percentage === 100 && cdJob.result;
743
+ if (this.isFirstProgress) {
744
+ this.isFirstProgress = false;
745
+ await this.eventStore.appendEvent({
746
+ type: "job.started",
747
+ ...baseEvent,
748
+ payload: {
749
+ jobId: cdJob.id,
750
+ jobType: cdJob.type
751
+ }
752
+ });
753
+ } else if (isComplete) {
754
+ await this.eventStore.appendEvent({
755
+ type: "job.completed",
756
+ ...baseEvent,
757
+ payload: {
758
+ jobId: cdJob.id,
759
+ jobType: cdJob.type,
760
+ result: cdJob.result
761
+ }
762
+ });
763
+ } else {
764
+ await this.eventStore.appendEvent({
765
+ type: "job.progress",
766
+ ...baseEvent,
767
+ payload: {
768
+ jobId: cdJob.id,
769
+ jobType: cdJob.type,
770
+ progress: cdJob.progress
771
+ }
772
+ });
773
+ }
774
+ }
775
+ async handleJobFailure(job, error) {
776
+ await super.handleJobFailure(job, error);
777
+ if (job.status === "failed" && job.type === "comment-detection") {
778
+ const cdJob = job;
779
+ await this.eventStore.appendEvent({
780
+ type: "job.failed",
781
+ resourceId: cdJob.resourceId,
782
+ userId: cdJob.userId,
783
+ version: 1,
784
+ payload: {
785
+ jobId: cdJob.id,
786
+ jobType: cdJob.type,
787
+ error: "Comment detection failed. Please try again later."
788
+ }
789
+ });
790
+ }
791
+ }
792
+ async processCommentDetectionJob(job) {
793
+ console.log(`[CommentDetectionWorker] Processing comment detection for resource ${job.resourceId} (job: ${job.id})`);
794
+ const resource = await ResourceContext.getResourceMetadata(job.resourceId, this.config);
795
+ if (!resource) {
796
+ throw new Error(`Resource ${job.resourceId} not found`);
797
+ }
798
+ job.progress = {
799
+ stage: "analyzing",
800
+ percentage: 10,
801
+ message: "Loading resource..."
802
+ };
803
+ await this.updateJobProgress(job);
804
+ job.progress = {
805
+ stage: "analyzing",
806
+ percentage: 30,
807
+ message: "Analyzing text and generating comments..."
808
+ };
809
+ await this.updateJobProgress(job);
810
+ const comments = await AnnotationDetection.detectComments(
811
+ job.resourceId,
812
+ this.config,
813
+ job.instructions,
814
+ job.tone,
815
+ job.density
816
+ );
817
+ console.log(`[CommentDetectionWorker] Found ${comments.length} comments to create`);
818
+ job.progress = {
819
+ stage: "creating",
820
+ percentage: 60,
821
+ message: `Creating ${comments.length} annotations...`
822
+ };
823
+ await this.updateJobProgress(job);
824
+ let created = 0;
825
+ for (const comment of comments) {
826
+ try {
827
+ await this.createCommentAnnotation(job.resourceId, job.userId, comment);
828
+ created++;
829
+ } catch (error) {
830
+ console.error(`[CommentDetectionWorker] Failed to create comment:`, error);
831
+ }
832
+ }
833
+ job.result = {
834
+ commentsFound: comments.length,
835
+ commentsCreated: created
836
+ };
837
+ job.progress = {
838
+ stage: "creating",
839
+ percentage: 100,
840
+ message: `Complete! Created ${created} comments`
841
+ };
842
+ await this.updateJobProgress(job);
843
+ console.log(`[CommentDetectionWorker] \u2705 Created ${created}/${comments.length} comments`);
844
+ }
845
+ async createCommentAnnotation(resourceId2, userId_, comment) {
846
+ const backendUrl = this.config.services.backend?.publicURL;
847
+ if (!backendUrl) {
848
+ throw new Error("Backend publicURL not configured");
849
+ }
850
+ const resourceUri2 = resourceIdToURI2(resourceId2, backendUrl);
851
+ const annotationId2 = generateAnnotationId(backendUrl);
852
+ const annotation = {
853
+ "@context": "http://www.w3.org/ns/anno.jsonld",
854
+ type: "Annotation",
855
+ id: annotationId2,
856
+ motivation: "commenting",
857
+ target: {
858
+ type: "SpecificResource",
859
+ source: resourceUri2,
860
+ selector: [
861
+ {
862
+ type: "TextPositionSelector",
863
+ start: comment.start,
864
+ end: comment.end
865
+ },
866
+ {
867
+ type: "TextQuoteSelector",
868
+ exact: comment.exact,
869
+ prefix: comment.prefix || "",
870
+ suffix: comment.suffix || ""
871
+ }
872
+ ]
873
+ },
874
+ body: [
875
+ {
876
+ type: "TextualBody",
877
+ value: comment.comment,
878
+ purpose: "commenting",
879
+ format: "text/plain",
880
+ language: "en"
881
+ }
882
+ ]
883
+ };
884
+ await this.eventStore.appendEvent({
885
+ type: "annotation.added",
886
+ resourceId: resourceId2,
887
+ userId: userId(userId_),
888
+ version: 1,
889
+ payload: {
890
+ annotation
891
+ }
892
+ });
893
+ console.log(`[CommentDetectionWorker] Created comment annotation ${annotationId2} for "${comment.exact.substring(0, 50)}..."`);
894
+ }
895
+ };
896
+
897
+ // src/jobs/workers/highlight-detection-worker.ts
898
+ import { JobWorker as JobWorker2 } from "@semiont/jobs";
899
+ import { generateAnnotationId as generateAnnotationId2 } from "@semiont/event-sourcing";
900
+ import { resourceIdToURI as resourceIdToURI3 } from "@semiont/core";
901
+ import { userId as userId2 } from "@semiont/core";
902
+ var HighlightDetectionWorker = class extends JobWorker2 {
903
+ constructor(jobQueue, config, eventStore) {
904
+ super(jobQueue);
905
+ this.config = config;
906
+ this.eventStore = eventStore;
907
+ }
908
+ isFirstProgress = true;
909
+ getWorkerName() {
910
+ return "HighlightDetectionWorker";
911
+ }
912
+ canProcessJob(job) {
913
+ return job.type === "highlight-detection";
914
+ }
915
+ async executeJob(job) {
916
+ if (job.type !== "highlight-detection") {
917
+ throw new Error(`Invalid job type: ${job.type}`);
918
+ }
919
+ this.isFirstProgress = true;
920
+ await this.processHighlightDetectionJob(job);
921
+ }
922
+ /**
923
+ * Override updateJobProgress to emit events to Event Store
924
+ */
925
+ async updateJobProgress(job) {
926
+ await super.updateJobProgress(job);
927
+ if (job.type !== "highlight-detection") return;
928
+ const hlJob = job;
929
+ if (!hlJob.progress) return;
930
+ const baseEvent = {
931
+ resourceId: hlJob.resourceId,
932
+ userId: hlJob.userId,
933
+ version: 1
934
+ };
935
+ const isComplete = hlJob.progress.percentage === 100 && hlJob.result;
936
+ if (this.isFirstProgress) {
937
+ this.isFirstProgress = false;
938
+ await this.eventStore.appendEvent({
939
+ type: "job.started",
940
+ ...baseEvent,
941
+ payload: {
942
+ jobId: hlJob.id,
943
+ jobType: hlJob.type
944
+ }
945
+ });
946
+ } else if (isComplete) {
947
+ await this.eventStore.appendEvent({
948
+ type: "job.completed",
949
+ ...baseEvent,
950
+ payload: {
951
+ jobId: hlJob.id,
952
+ jobType: hlJob.type,
953
+ result: hlJob.result
954
+ }
955
+ });
956
+ } else {
957
+ await this.eventStore.appendEvent({
958
+ type: "job.progress",
959
+ ...baseEvent,
960
+ payload: {
961
+ jobId: hlJob.id,
962
+ jobType: hlJob.type,
963
+ progress: hlJob.progress
964
+ }
965
+ });
966
+ }
967
+ }
968
+ async handleJobFailure(job, error) {
969
+ await super.handleJobFailure(job, error);
970
+ if (job.status === "failed" && job.type === "highlight-detection") {
971
+ const hlJob = job;
972
+ await this.eventStore.appendEvent({
973
+ type: "job.failed",
974
+ resourceId: hlJob.resourceId,
975
+ userId: hlJob.userId,
976
+ version: 1,
977
+ payload: {
978
+ jobId: hlJob.id,
979
+ jobType: hlJob.type,
980
+ error: "Highlight detection failed. Please try again later."
981
+ }
982
+ });
983
+ }
984
+ }
985
+ async processHighlightDetectionJob(job) {
986
+ console.log(`[HighlightDetectionWorker] Processing highlight detection for resource ${job.resourceId} (job: ${job.id})`);
987
+ const resource = await ResourceContext.getResourceMetadata(job.resourceId, this.config);
988
+ if (!resource) {
989
+ throw new Error(`Resource ${job.resourceId} not found`);
990
+ }
991
+ job.progress = {
992
+ stage: "analyzing",
993
+ percentage: 10,
994
+ message: "Loading resource..."
995
+ };
996
+ await this.updateJobProgress(job);
997
+ job.progress = {
998
+ stage: "analyzing",
999
+ percentage: 30,
1000
+ message: "Analyzing text..."
1001
+ };
1002
+ await this.updateJobProgress(job);
1003
+ const highlights = await AnnotationDetection.detectHighlights(
1004
+ job.resourceId,
1005
+ this.config,
1006
+ job.instructions,
1007
+ job.density
1008
+ );
1009
+ console.log(`[HighlightDetectionWorker] Found ${highlights.length} highlights to create`);
1010
+ job.progress = {
1011
+ stage: "creating",
1012
+ percentage: 60,
1013
+ message: `Creating ${highlights.length} annotations...`
1014
+ };
1015
+ await this.updateJobProgress(job);
1016
+ let created = 0;
1017
+ for (const highlight of highlights) {
1018
+ try {
1019
+ await this.createHighlightAnnotation(job.resourceId, job.userId, highlight);
1020
+ created++;
1021
+ } catch (error) {
1022
+ console.error(`[HighlightDetectionWorker] Failed to create highlight:`, error);
1023
+ }
1024
+ }
1025
+ job.result = {
1026
+ highlightsFound: highlights.length,
1027
+ highlightsCreated: created
1028
+ };
1029
+ job.progress = {
1030
+ stage: "creating",
1031
+ percentage: 100,
1032
+ message: `Complete! Created ${created} highlights`
1033
+ };
1034
+ await this.updateJobProgress(job);
1035
+ console.log(`[HighlightDetectionWorker] \u2705 Created ${created}/${highlights.length} highlights`);
1036
+ }
1037
+ async createHighlightAnnotation(resourceId2, creatorUserId, highlight) {
1038
+ const backendUrl = this.config.services.backend?.publicURL;
1039
+ if (!backendUrl) throw new Error("Backend publicURL not configured");
1040
+ const annotationId2 = generateAnnotationId2(backendUrl);
1041
+ const resourceUri2 = resourceIdToURI3(resourceId2, backendUrl);
1042
+ const annotation = {
1043
+ "@context": "http://www.w3.org/ns/anno.jsonld",
1044
+ "type": "Annotation",
1045
+ "id": annotationId2,
1046
+ "motivation": "highlighting",
1047
+ "creator": userId2(creatorUserId),
1048
+ "created": (/* @__PURE__ */ new Date()).toISOString(),
1049
+ "target": {
1050
+ type: "SpecificResource",
1051
+ source: resourceUri2,
1052
+ selector: [
1053
+ {
1054
+ type: "TextPositionSelector",
1055
+ start: highlight.start,
1056
+ end: highlight.end
1057
+ },
1058
+ {
1059
+ type: "TextQuoteSelector",
1060
+ exact: highlight.exact,
1061
+ ...highlight.prefix && { prefix: highlight.prefix },
1062
+ ...highlight.suffix && { suffix: highlight.suffix }
1063
+ }
1064
+ ]
1065
+ },
1066
+ "body": []
1067
+ // Empty body for highlights
1068
+ };
1069
+ await this.eventStore.appendEvent({
1070
+ type: "annotation.added",
1071
+ resourceId: resourceId2,
1072
+ userId: userId2(creatorUserId),
1073
+ version: 1,
1074
+ payload: { annotation }
1075
+ });
1076
+ }
1077
+ };
1078
+
1079
+ // src/jobs/workers/assessment-detection-worker.ts
1080
+ import { JobWorker as JobWorker3 } from "@semiont/jobs";
1081
+ import { generateAnnotationId as generateAnnotationId3 } from "@semiont/event-sourcing";
1082
+ import { resourceIdToURI as resourceIdToURI4 } from "@semiont/core";
1083
+ import { userId as userId3 } from "@semiont/core";
1084
+ var AssessmentDetectionWorker = class extends JobWorker3 {
1085
+ constructor(jobQueue, config, eventStore) {
1086
+ super(jobQueue);
1087
+ this.config = config;
1088
+ this.eventStore = eventStore;
1089
+ }
1090
+ isFirstProgress = true;
1091
+ getWorkerName() {
1092
+ return "AssessmentDetectionWorker";
1093
+ }
1094
+ canProcessJob(job) {
1095
+ return job.type === "assessment-detection";
1096
+ }
1097
+ async executeJob(job) {
1098
+ if (job.type !== "assessment-detection") {
1099
+ throw new Error(`Invalid job type: ${job.type}`);
1100
+ }
1101
+ this.isFirstProgress = true;
1102
+ await this.processAssessmentDetectionJob(job);
1103
+ }
1104
+ /**
1105
+ * Override updateJobProgress to emit events to Event Store
1106
+ */
1107
+ async updateJobProgress(job) {
1108
+ await super.updateJobProgress(job);
1109
+ if (job.type !== "assessment-detection") return;
1110
+ const assJob = job;
1111
+ if (!assJob.progress) return;
1112
+ const baseEvent = {
1113
+ resourceId: assJob.resourceId,
1114
+ userId: assJob.userId,
1115
+ version: 1
1116
+ };
1117
+ const isComplete = assJob.progress.percentage === 100 && assJob.result;
1118
+ if (this.isFirstProgress) {
1119
+ this.isFirstProgress = false;
1120
+ await this.eventStore.appendEvent({
1121
+ type: "job.started",
1122
+ ...baseEvent,
1123
+ payload: {
1124
+ jobId: assJob.id,
1125
+ jobType: assJob.type
1126
+ }
1127
+ });
1128
+ } else if (isComplete) {
1129
+ await this.eventStore.appendEvent({
1130
+ type: "job.completed",
1131
+ ...baseEvent,
1132
+ payload: {
1133
+ jobId: assJob.id,
1134
+ jobType: assJob.type,
1135
+ result: assJob.result
1136
+ }
1137
+ });
1138
+ } else {
1139
+ await this.eventStore.appendEvent({
1140
+ type: "job.progress",
1141
+ ...baseEvent,
1142
+ payload: {
1143
+ jobId: assJob.id,
1144
+ jobType: assJob.type,
1145
+ progress: assJob.progress
1146
+ }
1147
+ });
1148
+ }
1149
+ }
1150
+ async handleJobFailure(job, error) {
1151
+ await super.handleJobFailure(job, error);
1152
+ if (job.status === "failed" && job.type === "assessment-detection") {
1153
+ const aJob = job;
1154
+ await this.eventStore.appendEvent({
1155
+ type: "job.failed",
1156
+ resourceId: aJob.resourceId,
1157
+ userId: aJob.userId,
1158
+ version: 1,
1159
+ payload: {
1160
+ jobId: aJob.id,
1161
+ jobType: aJob.type,
1162
+ error: "Assessment detection failed. Please try again later."
1163
+ }
1164
+ });
1165
+ }
1166
+ }
1167
+ async processAssessmentDetectionJob(job) {
1168
+ console.log(`[AssessmentDetectionWorker] Processing assessment detection for resource ${job.resourceId} (job: ${job.id})`);
1169
+ const resource = await ResourceContext.getResourceMetadata(job.resourceId, this.config);
1170
+ if (!resource) {
1171
+ throw new Error(`Resource ${job.resourceId} not found`);
1172
+ }
1173
+ job.progress = {
1174
+ stage: "analyzing",
1175
+ percentage: 10,
1176
+ message: "Loading resource..."
1177
+ };
1178
+ await this.updateJobProgress(job);
1179
+ job.progress = {
1180
+ stage: "analyzing",
1181
+ percentage: 30,
1182
+ message: "Analyzing text..."
1183
+ };
1184
+ await this.updateJobProgress(job);
1185
+ const assessments = await AnnotationDetection.detectAssessments(
1186
+ job.resourceId,
1187
+ this.config,
1188
+ job.instructions,
1189
+ job.tone,
1190
+ job.density
1191
+ );
1192
+ console.log(`[AssessmentDetectionWorker] Found ${assessments.length} assessments to create`);
1193
+ job.progress = {
1194
+ stage: "creating",
1195
+ percentage: 60,
1196
+ message: `Creating ${assessments.length} annotations...`
1197
+ };
1198
+ await this.updateJobProgress(job);
1199
+ let created = 0;
1200
+ for (const assessment of assessments) {
1201
+ try {
1202
+ await this.createAssessmentAnnotation(job.resourceId, job.userId, assessment);
1203
+ created++;
1204
+ } catch (error) {
1205
+ console.error(`[AssessmentDetectionWorker] Failed to create assessment:`, error);
1206
+ }
1207
+ }
1208
+ job.result = {
1209
+ assessmentsFound: assessments.length,
1210
+ assessmentsCreated: created
1211
+ };
1212
+ job.progress = {
1213
+ stage: "creating",
1214
+ percentage: 100,
1215
+ message: `Complete! Created ${created} assessments`
1216
+ };
1217
+ await this.updateJobProgress(job);
1218
+ console.log(`[AssessmentDetectionWorker] \u2705 Created ${created}/${assessments.length} assessments`);
1219
+ }
1220
+ async createAssessmentAnnotation(resourceId2, creatorUserId, assessment) {
1221
+ const backendUrl = this.config.services.backend?.publicURL;
1222
+ if (!backendUrl) throw new Error("Backend publicURL not configured");
1223
+ const annotationId2 = generateAnnotationId3(backendUrl);
1224
+ const resourceUri2 = resourceIdToURI4(resourceId2, backendUrl);
1225
+ const annotation = {
1226
+ "@context": "http://www.w3.org/ns/anno.jsonld",
1227
+ "type": "Annotation",
1228
+ "id": annotationId2,
1229
+ "motivation": "assessing",
1230
+ "creator": userId3(creatorUserId),
1231
+ "created": (/* @__PURE__ */ new Date()).toISOString(),
1232
+ "target": {
1233
+ type: "SpecificResource",
1234
+ source: resourceUri2,
1235
+ selector: [
1236
+ {
1237
+ type: "TextPositionSelector",
1238
+ start: assessment.start,
1239
+ end: assessment.end
1240
+ },
1241
+ {
1242
+ type: "TextQuoteSelector",
1243
+ exact: assessment.exact,
1244
+ ...assessment.prefix && { prefix: assessment.prefix },
1245
+ ...assessment.suffix && { suffix: assessment.suffix }
1246
+ }
1247
+ ]
1248
+ },
1249
+ "body": {
1250
+ type: "TextualBody",
1251
+ value: assessment.assessment,
1252
+ format: "text/plain"
1253
+ }
1254
+ };
1255
+ await this.eventStore.appendEvent({
1256
+ type: "annotation.added",
1257
+ resourceId: resourceId2,
1258
+ userId: userId3(creatorUserId),
1259
+ version: 1,
1260
+ payload: { annotation }
1261
+ });
1262
+ }
1263
+ };
1264
+
1265
+ // src/jobs/workers/tag-detection-worker.ts
1266
+ import { JobWorker as JobWorker4 } from "@semiont/jobs";
1267
+ import { generateAnnotationId as generateAnnotationId4 } from "@semiont/event-sourcing";
1268
+ import { resourceIdToURI as resourceIdToURI5 } from "@semiont/core";
1269
+ import { getTagSchema as getTagSchema2 } from "@semiont/ontology";
1270
+ import { userId as userId4 } from "@semiont/core";
1271
+ var TagDetectionWorker = class extends JobWorker4 {
1272
+ constructor(jobQueue, config, eventStore) {
1273
+ super(jobQueue);
1274
+ this.config = config;
1275
+ this.eventStore = eventStore;
1276
+ }
1277
+ isFirstProgress = true;
1278
+ getWorkerName() {
1279
+ return "TagDetectionWorker";
1280
+ }
1281
+ canProcessJob(job) {
1282
+ return job.type === "tag-detection";
1283
+ }
1284
+ async executeJob(job) {
1285
+ if (job.type !== "tag-detection") {
1286
+ throw new Error(`Invalid job type: ${job.type}`);
1287
+ }
1288
+ this.isFirstProgress = true;
1289
+ await this.processTagDetectionJob(job);
1290
+ }
1291
+ /**
1292
+ * Override updateJobProgress to emit events to Event Store
1293
+ */
1294
+ async updateJobProgress(job) {
1295
+ await super.updateJobProgress(job);
1296
+ if (job.type !== "tag-detection") return;
1297
+ const tdJob = job;
1298
+ if (!tdJob.progress) return;
1299
+ const baseEvent = {
1300
+ resourceId: tdJob.resourceId,
1301
+ userId: tdJob.userId,
1302
+ version: 1
1303
+ };
1304
+ const isComplete = tdJob.progress.percentage === 100 && tdJob.result;
1305
+ if (this.isFirstProgress) {
1306
+ this.isFirstProgress = false;
1307
+ await this.eventStore.appendEvent({
1308
+ type: "job.started",
1309
+ ...baseEvent,
1310
+ payload: {
1311
+ jobId: tdJob.id,
1312
+ jobType: tdJob.type
1313
+ }
1314
+ });
1315
+ } else if (isComplete) {
1316
+ await this.eventStore.appendEvent({
1317
+ type: "job.completed",
1318
+ ...baseEvent,
1319
+ payload: {
1320
+ jobId: tdJob.id,
1321
+ jobType: tdJob.type,
1322
+ result: tdJob.result
1323
+ }
1324
+ });
1325
+ } else {
1326
+ await this.eventStore.appendEvent({
1327
+ type: "job.progress",
1328
+ ...baseEvent,
1329
+ payload: {
1330
+ jobId: tdJob.id,
1331
+ jobType: tdJob.type,
1332
+ progress: tdJob.progress
1333
+ }
1334
+ });
1335
+ }
1336
+ }
1337
+ async handleJobFailure(job, error) {
1338
+ await super.handleJobFailure(job, error);
1339
+ if (job.status === "failed" && job.type === "tag-detection") {
1340
+ const tdJob = job;
1341
+ await this.eventStore.appendEvent({
1342
+ type: "job.failed",
1343
+ resourceId: tdJob.resourceId,
1344
+ userId: tdJob.userId,
1345
+ version: 1,
1346
+ payload: {
1347
+ jobId: tdJob.id,
1348
+ jobType: tdJob.type,
1349
+ error: "Tag detection failed. Please try again later."
1350
+ }
1351
+ });
1352
+ }
1353
+ }
1354
+ async processTagDetectionJob(job) {
1355
+ console.log(`[TagDetectionWorker] Processing tag detection for resource ${job.resourceId} (job: ${job.id})`);
1356
+ const schema = getTagSchema2(job.schemaId);
1357
+ if (!schema) {
1358
+ throw new Error(`Invalid tag schema: ${job.schemaId}`);
1359
+ }
1360
+ for (const category of job.categories) {
1361
+ if (!schema.tags.some((t) => t.name === category)) {
1362
+ throw new Error(`Invalid category "${category}" for schema ${job.schemaId}`);
1363
+ }
1364
+ }
1365
+ const resource = await ResourceContext.getResourceMetadata(job.resourceId, this.config);
1366
+ if (!resource) {
1367
+ throw new Error(`Resource ${job.resourceId} not found`);
1368
+ }
1369
+ job.progress = {
1370
+ stage: "analyzing",
1371
+ percentage: 10,
1372
+ processedCategories: 0,
1373
+ totalCategories: job.categories.length,
1374
+ message: "Loading resource..."
1375
+ };
1376
+ await this.updateJobProgress(job);
1377
+ const allTags = [];
1378
+ const byCategory = {};
1379
+ for (let i = 0; i < job.categories.length; i++) {
1380
+ const category = job.categories[i];
1381
+ job.progress = {
1382
+ stage: "analyzing",
1383
+ percentage: 10 + Math.floor(i / job.categories.length * 50),
1384
+ currentCategory: category,
1385
+ processedCategories: i + 1,
1386
+ totalCategories: job.categories.length,
1387
+ message: `Analyzing ${category}...`
1388
+ };
1389
+ await this.updateJobProgress(job);
1390
+ const tags = await AnnotationDetection.detectTags(
1391
+ job.resourceId,
1392
+ this.config,
1393
+ job.schemaId,
1394
+ category
1395
+ );
1396
+ console.log(`[TagDetectionWorker] Found ${tags.length} tags for category "${category}"`);
1397
+ allTags.push(...tags);
1398
+ byCategory[category] = tags.length;
1399
+ }
1400
+ job.progress = {
1401
+ stage: "creating",
1402
+ percentage: 60,
1403
+ processedCategories: job.categories.length,
1404
+ totalCategories: job.categories.length,
1405
+ message: `Creating ${allTags.length} tag annotations...`
1406
+ };
1407
+ await this.updateJobProgress(job);
1408
+ let created = 0;
1409
+ for (const tag of allTags) {
1410
+ try {
1411
+ await this.createTagAnnotation(job.resourceId, job.userId, job.schemaId, tag);
1412
+ created++;
1413
+ } catch (error) {
1414
+ console.error(`[TagDetectionWorker] Failed to create tag:`, error);
1415
+ }
1416
+ }
1417
+ job.result = {
1418
+ tagsFound: allTags.length,
1419
+ tagsCreated: created,
1420
+ byCategory
1421
+ };
1422
+ job.progress = {
1423
+ stage: "creating",
1424
+ percentage: 100,
1425
+ processedCategories: job.categories.length,
1426
+ totalCategories: job.categories.length,
1427
+ message: `Complete! Created ${created} tags`
1428
+ };
1429
+ await this.updateJobProgress(job);
1430
+ console.log(`[TagDetectionWorker] \u2705 Created ${created}/${allTags.length} tags across ${job.categories.length} categories`);
1431
+ }
1432
+ async createTagAnnotation(resourceId2, userId_, schemaId, tag) {
1433
+ const backendUrl = this.config.services.backend?.publicURL;
1434
+ if (!backendUrl) {
1435
+ throw new Error("Backend publicURL not configured");
1436
+ }
1437
+ const resourceUri2 = resourceIdToURI5(resourceId2, backendUrl);
1438
+ const annotationId2 = generateAnnotationId4(backendUrl);
1439
+ const annotation = {
1440
+ "@context": "http://www.w3.org/ns/anno.jsonld",
1441
+ type: "Annotation",
1442
+ id: annotationId2,
1443
+ motivation: "tagging",
1444
+ target: {
1445
+ type: "SpecificResource",
1446
+ source: resourceUri2,
1447
+ selector: [
1448
+ {
1449
+ type: "TextPositionSelector",
1450
+ start: tag.start,
1451
+ end: tag.end
1452
+ },
1453
+ {
1454
+ type: "TextQuoteSelector",
1455
+ exact: tag.exact,
1456
+ prefix: tag.prefix || "",
1457
+ suffix: tag.suffix || ""
1458
+ }
1459
+ ]
1460
+ },
1461
+ body: [
1462
+ {
1463
+ type: "TextualBody",
1464
+ value: tag.category,
1465
+ purpose: "tagging",
1466
+ format: "text/plain",
1467
+ language: "en"
1468
+ },
1469
+ {
1470
+ type: "TextualBody",
1471
+ value: schemaId,
1472
+ purpose: "classifying",
1473
+ format: "text/plain"
1474
+ }
1475
+ ]
1476
+ };
1477
+ await this.eventStore.appendEvent({
1478
+ type: "annotation.added",
1479
+ resourceId: resourceId2,
1480
+ userId: userId4(userId_),
1481
+ version: 1,
1482
+ payload: {
1483
+ annotation
1484
+ }
1485
+ });
1486
+ console.log(`[TagDetectionWorker] Created tag annotation ${annotationId2} for "${tag.category}": "${tag.exact.substring(0, 50)}..."`);
1487
+ }
1488
+ };
1489
+
1490
+ // src/jobs/workers/reference-detection-worker.ts
1491
+ import { JobWorker as JobWorker5 } from "@semiont/jobs";
1492
+ import { generateAnnotationId as generateAnnotationId5 } from "@semiont/event-sourcing";
1493
+ import { resourceIdToURI as resourceIdToURI6 } from "@semiont/core";
1494
+ import {
1495
+ getPrimaryRepresentation as getPrimaryRepresentation4,
1496
+ decodeRepresentation as decodeRepresentation4,
1497
+ validateAndCorrectOffsets
1498
+ } from "@semiont/api-client";
1499
+ import { extractEntities } from "@semiont/inference";
1500
+ import { FilesystemRepresentationStore as FilesystemRepresentationStore4 } from "@semiont/content";
1501
+ var ReferenceDetectionWorker = class extends JobWorker5 {
1502
+ constructor(jobQueue, config, eventStore) {
1503
+ super(jobQueue);
1504
+ this.config = config;
1505
+ this.eventStore = eventStore;
1506
+ }
1507
+ getWorkerName() {
1508
+ return "ReferenceDetectionWorker";
1509
+ }
1510
+ canProcessJob(job) {
1511
+ return job.type === "detection";
1512
+ }
1513
+ async executeJob(job) {
1514
+ if (job.type !== "detection") {
1515
+ throw new Error(`Invalid job type: ${job.type}`);
1516
+ }
1517
+ await this.processDetectionJob(job);
1518
+ }
1519
+ /**
1520
+ * Detect entity references in resource using AI
1521
+ * Self-contained implementation for reference detection
1522
+ *
1523
+ * Public for testing charset handling - see entity-detection-charset.test.ts
1524
+ */
1525
+ async detectReferences(resource, entityTypes, includeDescriptiveReferences = false) {
1526
+ console.log(`Detecting entities of types: ${entityTypes.join(", ")}${includeDescriptiveReferences ? " (including descriptive references)" : ""}`);
1527
+ const detectedAnnotations = [];
1528
+ const primaryRep = getPrimaryRepresentation4(resource);
1529
+ if (!primaryRep) return detectedAnnotations;
1530
+ const mediaType = primaryRep.mediaType;
1531
+ const baseMediaType = mediaType?.split(";")[0]?.trim() || "";
1532
+ if (baseMediaType === "text/plain" || baseMediaType === "text/markdown") {
1533
+ if (!primaryRep.checksum || !primaryRep.mediaType) return detectedAnnotations;
1534
+ const basePath = this.config.services.filesystem.path;
1535
+ const projectRoot = this.config._metadata?.projectRoot;
1536
+ const repStore = new FilesystemRepresentationStore4({ basePath }, projectRoot);
1537
+ const contentBuffer = await repStore.retrieve(primaryRep.checksum, primaryRep.mediaType);
1538
+ const content = decodeRepresentation4(contentBuffer, primaryRep.mediaType);
1539
+ const extractedEntities = await extractEntities(content, entityTypes, this.config, includeDescriptiveReferences);
1540
+ for (const entity of extractedEntities) {
1541
+ try {
1542
+ const validated = validateAndCorrectOffsets(
1543
+ content,
1544
+ entity.startOffset,
1545
+ entity.endOffset,
1546
+ entity.exact
1547
+ );
1548
+ const annotation = {
1549
+ annotation: {
1550
+ selector: {
1551
+ start: validated.start,
1552
+ end: validated.end,
1553
+ exact: validated.exact,
1554
+ prefix: validated.prefix,
1555
+ suffix: validated.suffix
1556
+ },
1557
+ entityTypes: [entity.entityType]
1558
+ }
1559
+ };
1560
+ detectedAnnotations.push(annotation);
1561
+ } catch (error) {
1562
+ console.warn(`[ReferenceDetectionWorker] Skipping invalid entity "${entity.exact}":`, error);
1563
+ }
1564
+ }
1565
+ }
1566
+ return detectedAnnotations;
1567
+ }
1568
+ async processDetectionJob(job) {
1569
+ console.log(`[ReferenceDetectionWorker] Processing detection for resource ${job.resourceId} (job: ${job.id})`);
1570
+ console.log(`[ReferenceDetectionWorker] \u{1F50D} Entity types: ${job.entityTypes.join(", ")}`);
1571
+ const resource = await ResourceContext.getResourceMetadata(job.resourceId, this.config);
1572
+ if (!resource) {
1573
+ throw new Error(`Resource ${job.resourceId} not found`);
1574
+ }
1575
+ let totalFound = 0;
1576
+ let totalEmitted = 0;
1577
+ let totalErrors = 0;
1578
+ job.progress = {
1579
+ totalEntityTypes: job.entityTypes.length,
1580
+ processedEntityTypes: 0,
1581
+ entitiesFound: 0,
1582
+ entitiesEmitted: 0
1583
+ };
1584
+ await this.updateJobProgress(job);
1585
+ for (let i = 0; i < job.entityTypes.length; i++) {
1586
+ const entityType = job.entityTypes[i];
1587
+ if (!entityType) continue;
1588
+ console.log(`[ReferenceDetectionWorker] \u{1F916} [${i + 1}/${job.entityTypes.length}] Detecting ${entityType}...`);
1589
+ const detectedAnnotations = await this.detectReferences(resource, [entityType], job.includeDescriptiveReferences);
1590
+ totalFound += detectedAnnotations.length;
1591
+ console.log(`[ReferenceDetectionWorker] \u2705 Found ${detectedAnnotations.length} ${entityType} entities`);
1592
+ for (let idx = 0; idx < detectedAnnotations.length; idx++) {
1593
+ const detected = detectedAnnotations[idx];
1594
+ if (!detected) {
1595
+ console.warn(`[ReferenceDetectionWorker] Skipping undefined entity at index ${idx}`);
1596
+ continue;
1597
+ }
1598
+ let referenceId;
1599
+ try {
1600
+ const backendUrl = this.config.services.backend?.publicURL;
1601
+ if (!backendUrl) {
1602
+ throw new Error("Backend publicURL not configured");
1603
+ }
1604
+ referenceId = generateAnnotationId5(backendUrl);
1605
+ } catch (error) {
1606
+ console.error(`[ReferenceDetectionWorker] Failed to generate annotation ID:`, error);
1607
+ job.status = "failed";
1608
+ job.error = "Configuration error: Backend publicURL not set";
1609
+ await this.updateJobProgress(job);
1610
+ return;
1611
+ }
1612
+ try {
1613
+ await this.eventStore.appendEvent({
1614
+ type: "annotation.added",
1615
+ resourceId: job.resourceId,
1616
+ userId: job.userId,
1617
+ version: 1,
1618
+ payload: {
1619
+ annotation: {
1620
+ "@context": "http://www.w3.org/ns/anno.jsonld",
1621
+ "type": "Annotation",
1622
+ id: referenceId,
1623
+ motivation: "linking",
1624
+ target: {
1625
+ source: resourceIdToURI6(job.resourceId, this.config.services.backend.publicURL),
1626
+ // Convert to full URI
1627
+ selector: [
1628
+ {
1629
+ type: "TextPositionSelector",
1630
+ start: detected.annotation.selector.start,
1631
+ end: detected.annotation.selector.end
1632
+ },
1633
+ {
1634
+ type: "TextQuoteSelector",
1635
+ exact: detected.annotation.selector.exact,
1636
+ ...detected.annotation.selector.prefix && { prefix: detected.annotation.selector.prefix },
1637
+ ...detected.annotation.selector.suffix && { suffix: detected.annotation.selector.suffix }
1638
+ }
1639
+ ]
1640
+ },
1641
+ body: (detected.annotation.entityTypes || []).map((et) => ({
1642
+ type: "TextualBody",
1643
+ value: et,
1644
+ purpose: "tagging"
1645
+ })),
1646
+ modified: (/* @__PURE__ */ new Date()).toISOString()
1647
+ }
1648
+ }
1649
+ });
1650
+ totalEmitted++;
1651
+ if ((idx + 1) % 10 === 0 || idx === detectedAnnotations.length - 1) {
1652
+ console.log(`[ReferenceDetectionWorker] \u{1F4E4} Emitted ${idx + 1}/${detectedAnnotations.length} events for ${entityType}`);
1653
+ }
1654
+ } catch (error) {
1655
+ totalErrors++;
1656
+ console.error(`[ReferenceDetectionWorker] \u274C Failed to emit event for ${referenceId}:`, error);
1657
+ }
1658
+ }
1659
+ console.log(`[ReferenceDetectionWorker] \u2705 Completed ${entityType}: ${detectedAnnotations.length} found, ${detectedAnnotations.length - (totalErrors - (totalFound - totalEmitted))} emitted`);
1660
+ job.progress = {
1661
+ totalEntityTypes: job.entityTypes.length,
1662
+ processedEntityTypes: i + 1,
1663
+ currentEntityType: entityType,
1664
+ entitiesFound: totalFound,
1665
+ entitiesEmitted: totalEmitted
1666
+ };
1667
+ await this.updateJobProgress(job);
1668
+ }
1669
+ job.result = {
1670
+ totalFound,
1671
+ totalEmitted,
1672
+ errors: totalErrors
1673
+ };
1674
+ console.log(`[ReferenceDetectionWorker] \u2705 Detection complete: ${totalFound} entities found, ${totalEmitted} events emitted, ${totalErrors} errors`);
1675
+ }
1676
+ async handleJobFailure(job, error) {
1677
+ await super.handleJobFailure(job, error);
1678
+ if (job.status === "failed" && job.type === "detection") {
1679
+ const detJob = job;
1680
+ await this.eventStore.appendEvent({
1681
+ type: "job.failed",
1682
+ resourceId: detJob.resourceId,
1683
+ userId: detJob.userId,
1684
+ version: 1,
1685
+ payload: {
1686
+ jobId: detJob.id,
1687
+ jobType: detJob.type,
1688
+ error: "Entity detection failed. Please try again later."
1689
+ }
1690
+ });
1691
+ }
1692
+ }
1693
+ /**
1694
+ * Update job progress and emit events to Event Store
1695
+ * Overrides base class to also emit job progress events
1696
+ */
1697
+ async updateJobProgress(job) {
1698
+ await super.updateJobProgress(job);
1699
+ if (job.type !== "detection") {
1700
+ return;
1701
+ }
1702
+ const detJob = job;
1703
+ const baseEvent = {
1704
+ resourceId: detJob.resourceId,
1705
+ userId: detJob.userId,
1706
+ version: 1
1707
+ };
1708
+ if (!detJob.progress) {
1709
+ return;
1710
+ }
1711
+ const isFirstUpdate = detJob.progress.processedEntityTypes === 0;
1712
+ const isFinalUpdate = detJob.progress.processedEntityTypes === detJob.progress.totalEntityTypes && detJob.progress.totalEntityTypes > 0;
1713
+ if (isFirstUpdate) {
1714
+ await this.eventStore.appendEvent({
1715
+ type: "job.started",
1716
+ ...baseEvent,
1717
+ payload: {
1718
+ jobId: detJob.id,
1719
+ jobType: detJob.type,
1720
+ totalSteps: detJob.entityTypes.length
1721
+ }
1722
+ });
1723
+ } else if (isFinalUpdate) {
1724
+ await this.eventStore.appendEvent({
1725
+ type: "job.completed",
1726
+ ...baseEvent,
1727
+ payload: {
1728
+ jobId: detJob.id,
1729
+ jobType: detJob.type,
1730
+ foundCount: detJob.progress.entitiesFound
1731
+ }
1732
+ });
1733
+ } else {
1734
+ const percentage = Math.round(detJob.progress.processedEntityTypes / detJob.progress.totalEntityTypes * 100);
1735
+ await this.eventStore.appendEvent({
1736
+ type: "job.progress",
1737
+ ...baseEvent,
1738
+ payload: {
1739
+ jobId: detJob.id,
1740
+ jobType: detJob.type,
1741
+ percentage,
1742
+ currentStep: detJob.progress.currentEntityType,
1743
+ processedSteps: detJob.progress.processedEntityTypes,
1744
+ totalSteps: detJob.progress.totalEntityTypes,
1745
+ foundCount: detJob.progress.entitiesFound
1746
+ }
1747
+ });
1748
+ }
1749
+ }
1750
+ };
1751
+
1752
+ // src/jobs/workers/generation-worker.ts
1753
+ import { JobWorker as JobWorker6 } from "@semiont/jobs";
1754
+ import { FilesystemRepresentationStore as FilesystemRepresentationStore5 } from "@semiont/content";
1755
+ import { generateResourceFromTopic } from "@semiont/inference";
1756
+ import {
1757
+ getTargetSelector as getTargetSelector2,
1758
+ getExactText,
1759
+ resourceUri,
1760
+ annotationUri
1761
+ } from "@semiont/api-client";
1762
+ import { getEntityTypes as getEntityTypes2 } from "@semiont/ontology";
1763
+ import {
1764
+ CREATION_METHODS,
1765
+ generateUuid,
1766
+ resourceId,
1767
+ annotationId
1768
+ } from "@semiont/core";
1769
+ var GenerationWorker = class extends JobWorker6 {
1770
+ constructor(jobQueue, config, eventStore) {
1771
+ super(jobQueue);
1772
+ this.config = config;
1773
+ this.eventStore = eventStore;
1774
+ }
1775
+ getWorkerName() {
1776
+ return "GenerationWorker";
1777
+ }
1778
+ canProcessJob(job) {
1779
+ return job.type === "generation";
1780
+ }
1781
+ async executeJob(job) {
1782
+ if (job.type !== "generation") {
1783
+ throw new Error(`Invalid job type: ${job.type}`);
1784
+ }
1785
+ await this.processGenerationJob(job);
1786
+ }
1787
+ async processGenerationJob(job) {
1788
+ console.log(`[GenerationWorker] Processing generation for reference ${job.referenceId} (job: ${job.id})`);
1789
+ const basePath = this.config.services.filesystem.path;
1790
+ const projectRoot = this.config._metadata?.projectRoot;
1791
+ const repStore = new FilesystemRepresentationStore5({ basePath }, projectRoot);
1792
+ job.progress = {
1793
+ stage: "fetching",
1794
+ percentage: 20,
1795
+ message: "Fetching source resource..."
1796
+ };
1797
+ console.log(`[GenerationWorker] \u{1F4E5} ${job.progress.message}`);
1798
+ await this.updateJobProgress(job);
1799
+ const { FilesystemViewStorage: FilesystemViewStorage3 } = await import("@semiont/event-sourcing");
1800
+ const viewStorage = new FilesystemViewStorage3(basePath, projectRoot);
1801
+ const view = await viewStorage.get(job.sourceResourceId);
1802
+ if (!view) {
1803
+ throw new Error(`Resource ${job.sourceResourceId} not found`);
1804
+ }
1805
+ const projection = view.annotations;
1806
+ const expectedAnnotationUri = `${this.config.services.backend.publicURL}/annotations/${job.referenceId}`;
1807
+ const annotation = projection.annotations.find(
1808
+ (a) => a.id === expectedAnnotationUri && a.motivation === "linking"
1809
+ );
1810
+ if (!annotation) {
1811
+ throw new Error(`Annotation ${job.referenceId} not found in resource ${job.sourceResourceId}`);
1812
+ }
1813
+ const sourceResource = await ResourceContext.getResourceMetadata(job.sourceResourceId, this.config);
1814
+ if (!sourceResource) {
1815
+ throw new Error(`Source resource ${job.sourceResourceId} not found`);
1816
+ }
1817
+ const targetSelector = getTargetSelector2(annotation.target);
1818
+ const resourceName = job.title || (targetSelector ? getExactText(targetSelector) : "") || "New Resource";
1819
+ console.log(`[GenerationWorker] Generating resource: "${resourceName}"`);
1820
+ if (!job.context) {
1821
+ throw new Error("Generation context is required but was not provided in job");
1822
+ }
1823
+ console.log(`[GenerationWorker] Using pre-fetched context: ${job.context.sourceContext?.before?.length || 0} chars before, ${job.context.sourceContext?.selected?.length || 0} chars selected, ${job.context.sourceContext?.after?.length || 0} chars after`);
1824
+ job.progress = {
1825
+ stage: "generating",
1826
+ percentage: 40,
1827
+ message: "Creating content with AI..."
1828
+ };
1829
+ console.log(`[GenerationWorker] \u{1F916} ${job.progress.message}`);
1830
+ await this.updateJobProgress(job);
1831
+ const prompt = job.prompt || `Create a comprehensive resource about "${resourceName}"`;
1832
+ const annotationEntityTypes = getEntityTypes2({ body: annotation.body });
1833
+ const generatedContent = await generateResourceFromTopic(
1834
+ resourceName,
1835
+ job.entityTypes || annotationEntityTypes,
1836
+ this.config,
1837
+ prompt,
1838
+ job.language,
1839
+ job.context,
1840
+ // NEW - context from job (passed from modal)
1841
+ job.temperature,
1842
+ // NEW - from job
1843
+ job.maxTokens
1844
+ // NEW - from job
1845
+ );
1846
+ console.log(`[GenerationWorker] \u2705 Generated ${generatedContent.content.length} bytes of content`);
1847
+ job.progress = {
1848
+ stage: "generating",
1849
+ percentage: 70,
1850
+ message: "Content ready, creating resource..."
1851
+ };
1852
+ await this.updateJobProgress(job);
1853
+ const rId = resourceId(generateUuid());
1854
+ job.progress = {
1855
+ stage: "creating",
1856
+ percentage: 85,
1857
+ message: "Saving resource..."
1858
+ };
1859
+ console.log(`[GenerationWorker] \u{1F4BE} ${job.progress.message}`);
1860
+ await this.updateJobProgress(job);
1861
+ const storedRep = await repStore.store(Buffer.from(generatedContent.content), {
1862
+ mediaType: "text/markdown",
1863
+ rel: "original"
1864
+ });
1865
+ console.log(`[GenerationWorker] \u2705 Saved resource representation to filesystem: ${rId}`);
1866
+ await this.eventStore.appendEvent({
1867
+ type: "resource.created",
1868
+ resourceId: rId,
1869
+ userId: job.userId,
1870
+ version: 1,
1871
+ payload: {
1872
+ name: resourceName,
1873
+ format: "text/markdown",
1874
+ contentChecksum: storedRep.checksum,
1875
+ creationMethod: CREATION_METHODS.GENERATED,
1876
+ entityTypes: job.entityTypes || annotationEntityTypes,
1877
+ language: job.language,
1878
+ isDraft: true,
1879
+ generatedFrom: job.referenceId,
1880
+ generationPrompt: void 0
1881
+ // Could be added if we track the prompt
1882
+ }
1883
+ });
1884
+ console.log(`[GenerationWorker] Emitted resource.created event for ${rId}`);
1885
+ job.progress = {
1886
+ stage: "linking",
1887
+ percentage: 95,
1888
+ message: "Linking reference..."
1889
+ };
1890
+ console.log(`[GenerationWorker] \u{1F517} ${job.progress.message}`);
1891
+ await this.updateJobProgress(job);
1892
+ const newResourceUri = resourceUri(`${this.config.services.backend.publicURL}/resources/${rId}`);
1893
+ const operations = [{
1894
+ op: "add",
1895
+ item: {
1896
+ type: "SpecificResource",
1897
+ source: newResourceUri,
1898
+ purpose: "linking"
1899
+ }
1900
+ }];
1901
+ const annotationIdSegment = job.referenceId.split("/").pop();
1902
+ await this.eventStore.appendEvent({
1903
+ type: "annotation.body.updated",
1904
+ resourceId: job.sourceResourceId,
1905
+ userId: job.userId,
1906
+ version: 1,
1907
+ payload: {
1908
+ annotationId: annotationId(annotationIdSegment),
1909
+ operations
1910
+ }
1911
+ });
1912
+ console.log(`[GenerationWorker] \u2705 Emitted annotation.body.updated event linking ${job.referenceId} \u2192 ${rId}`);
1913
+ job.result = {
1914
+ resourceId: rId,
1915
+ resourceName
1916
+ };
1917
+ job.progress = {
1918
+ stage: "linking",
1919
+ percentage: 100,
1920
+ message: "Complete!"
1921
+ };
1922
+ await this.updateJobProgress(job);
1923
+ console.log(`[GenerationWorker] \u2705 Generation complete: created resource ${rId}`);
1924
+ }
1925
+ /**
1926
+ * Update job progress and emit events to Event Store
1927
+ * Overrides base class to also emit job progress events
1928
+ */
1929
+ async updateJobProgress(job) {
1930
+ await super.updateJobProgress(job);
1931
+ if (job.type !== "generation") {
1932
+ return;
1933
+ }
1934
+ const genJob = job;
1935
+ const baseEvent = {
1936
+ resourceId: genJob.sourceResourceId,
1937
+ userId: genJob.userId,
1938
+ version: 1
1939
+ };
1940
+ if (genJob.progress?.stage === "fetching" && genJob.progress?.percentage === 20) {
1941
+ await this.eventStore.appendEvent({
1942
+ type: "job.started",
1943
+ ...baseEvent,
1944
+ payload: {
1945
+ jobId: genJob.id,
1946
+ jobType: genJob.type,
1947
+ totalSteps: 5
1948
+ // fetching, generating, creating, linking, complete
1949
+ }
1950
+ });
1951
+ } else if (genJob.progress?.stage === "linking" && genJob.progress?.percentage === 100) {
1952
+ await this.eventStore.appendEvent({
1953
+ type: "job.completed",
1954
+ ...baseEvent,
1955
+ payload: {
1956
+ jobId: genJob.id,
1957
+ jobType: genJob.type,
1958
+ resultResourceId: genJob.result?.resourceId,
1959
+ annotationUri: annotationUri(`${this.config.services.backend.publicURL}/annotations/${genJob.referenceId}`)
1960
+ }
1961
+ });
1962
+ } else if (genJob.progress) {
1963
+ await this.eventStore.appendEvent({
1964
+ type: "job.progress",
1965
+ ...baseEvent,
1966
+ payload: {
1967
+ jobId: genJob.id,
1968
+ jobType: genJob.type,
1969
+ currentStep: genJob.progress.stage,
1970
+ percentage: genJob.progress.percentage,
1971
+ message: genJob.progress.message
1972
+ }
1973
+ });
1974
+ }
1975
+ }
1976
+ };
1977
+
704
1978
  // src/index.ts
705
1979
  var PACKAGE_NAME = "@semiont/make-meaning";
706
1980
  var VERSION = "0.1.0";
707
1981
  export {
708
1982
  AnnotationContext,
709
1983
  AnnotationDetection,
1984
+ AssessmentDetectionWorker,
1985
+ CommentDetectionWorker,
1986
+ GenerationWorker,
710
1987
  GraphContext,
1988
+ HighlightDetectionWorker,
711
1989
  PACKAGE_NAME,
1990
+ ReferenceDetectionWorker,
712
1991
  ResourceContext,
1992
+ TagDetectionWorker,
713
1993
  VERSION
714
1994
  };
715
1995
  //# sourceMappingURL=index.js.map