@semiont/jobs 0.5.27 → 0.5.29

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 CHANGED
@@ -10,7 +10,7 @@ Job queue, worker infrastructure, and annotation workers for [Semiont](https://g
10
10
 
11
11
  ## Architecture Context
12
12
 
13
- Workers run in a separate process and connect to the Knowledge System (KS) over HTTP/SSE using a `SemiontSession` (from `@semiont/sdk`) driven by a `JobClaimAdapter`. Workers receive job assignments via an SSE `job:queued` subscription, claim jobs atomically, and emit domain events back to the KS via `session.client.transport.emit(...)`. The KS ingests these events onto its EventBus for SSE delivery to the frontend.
13
+ Workers run in a separate process and connect to the Knowledge System (KS) over HTTP/SSE using a `SemiontSession` (from `@semiont/sdk`) driven by a `JobClaimAdapter`. Workers receive job assignments via an SSE `job:queued` subscription, claim jobs atomically, and emit domain events back to the KS via `session.client.transport.emit(...)`. The KS ingests these events onto its EventBus for SSE delivery to the Browser.
14
14
 
15
15
  ## Installation
16
16
 
@@ -36,7 +36,9 @@ import { SemiontProject } from '@semiont/core/node';
36
36
 
37
37
  // Initialize — jobs are stored under project.jobsDir
38
38
  const eventBus = new EventBus();
39
- const project = new SemiontProject('/path/to/project');
39
+ const project = new SemiontProject('/path/to/project', {
40
+ anchoredTextDir: process.env.SEMIONT_ANCHORED_TEXT_DIR!,
41
+ });
40
42
  const jobQueue = new FsJobQueue(project, logger, eventBus);
41
43
  await jobQueue.initialize();
42
44
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { JobId, UserId, ResourceId, EntityType, GenerationJobParams, TagSchema, Logger, EventBus, components, Annotation, SupportedMediaType, GatheredContext } from '@semiont/core';
2
- import { SemiontProject } from '@semiont/core/node';
2
+ import { SemiontState } from '@semiont/core/node';
3
3
  import { InferenceClient } from '@semiont/inference';
4
4
 
5
5
  /**
@@ -136,6 +136,7 @@ interface DetectionProgress {
136
136
  * Detection job result
137
137
  */
138
138
  interface DetectionResult {
139
+ kind: 'reference-annotation';
139
140
  totalFound: number;
140
141
  totalEmitted: number;
141
142
  errors: number;
@@ -153,8 +154,11 @@ interface YieldProgress {
153
154
  * Generation job result
154
155
  */
155
156
  interface GenerationResult {
157
+ kind: 'generation';
156
158
  resourceId: ResourceId;
157
159
  resourceName: string;
160
+ /** True when the model stopped at the maxTokens ceiling — the artifact is cut off, not complete (GENERATE-FROM-RESOURCE D6). */
161
+ truncated: boolean;
158
162
  }
159
163
  /**
160
164
  * Highlight detection job progress
@@ -168,6 +172,7 @@ interface HighlightDetectionProgress {
168
172
  * Highlight detection job result
169
173
  */
170
174
  interface HighlightDetectionResult {
175
+ kind: 'highlight-annotation';
171
176
  highlightsFound: number;
172
177
  highlightsCreated: number;
173
178
  }
@@ -183,6 +188,7 @@ interface AssessmentDetectionProgress {
183
188
  * Assessment detection job result
184
189
  */
185
190
  interface AssessmentDetectionResult {
191
+ kind: 'assessment-annotation';
186
192
  assessmentsFound: number;
187
193
  assessmentsCreated: number;
188
194
  }
@@ -198,6 +204,7 @@ interface CommentDetectionProgress {
198
204
  * Comment detection job result
199
205
  */
200
206
  interface CommentDetectionResult {
207
+ kind: 'comment-annotation';
201
208
  commentsFound: number;
202
209
  commentsCreated: number;
203
210
  }
@@ -216,6 +223,7 @@ interface TagDetectionProgress {
216
223
  * Tag detection job result
217
224
  */
218
225
  interface TagDetectionResult {
226
+ kind: 'tag-annotation';
219
227
  tagsFound: number;
220
228
  tagsCreated: number;
221
229
  byCategory: Record<string, number>;
@@ -345,7 +353,10 @@ declare class FsJobQueue implements JobQueue {
345
353
  private cleanupTimer;
346
354
  /** Per-job timestamp of the last progress write, for throttling. */
347
355
  private lastProgressWrite;
348
- constructor(project: SemiontProject, logger: Logger, eventBus?: EventBus | undefined);
356
+ constructor(
357
+ /** `SemiontState` and not `SemiontProject`: the queue reads ONE path,
358
+ * and the gateway that owns it mounts no KB tree (SINGLE-KB-MOUNT P5). */
359
+ state: SemiontState, logger: Logger, eventBus?: EventBus | undefined);
349
360
  /**
350
361
  * Initialize job queue directories, announce any pending backlog,
351
362
  * and start the re-announce interval. Idempotent.
@@ -654,6 +665,7 @@ declare function generateResourceFromTopic(topic: string, entityTypes: string[],
654
665
  }): Promise<{
655
666
  title: string;
656
667
  content: string;
668
+ truncated: boolean;
657
669
  }>;
658
670
 
659
671
  /**
@@ -683,7 +695,7 @@ declare function generateResourceFromTopic(topic: string, entityTypes: string[],
683
695
  *
684
696
  * Thresholds are fixed by design (no env knobs) and deliberately
685
697
  * layered: inference timeout (10 min, P2) fires first; this watchdog
686
- * (15 min) catches the failure modes nobody predicted; the backend's
698
+ * (15 min) catches the failure modes nobody predicted; the gateway's
687
699
  * dead-worker janitor (30 min) re-queues the job regardless.
688
700
  */
689
701
  declare const STALL_THRESHOLD_MS: number;
package/dist/index.js CHANGED
@@ -17,9 +17,9 @@ var PROGRESS_WRITE_MIN_INTERVAL_MS = 5e3;
17
17
  var RETENTION_HOURS = 24;
18
18
  var CLEANUP_INTERVAL_MS = 36e5;
19
19
  var FsJobQueue = class {
20
- constructor(project, logger, eventBus) {
20
+ constructor(state, logger, eventBus) {
21
21
  this.eventBus = eventBus;
22
- this.jobsDir = project.jobsDir;
22
+ this.jobsDir = state.jobsDir;
23
23
  this.logger = logger;
24
24
  }
25
25
  eventBus;
@@ -468,9 +468,9 @@ async function withTimeout(work, label, onHeartbeat) {
468
468
  if (heartbeat) clearInterval(heartbeat);
469
469
  }
470
470
  }
471
- function boundedGenerate(client, prompt, maxTokens, temperature, onHeartbeat) {
471
+ function boundedGenerateWithMetadata(client, prompt, maxTokens, temperature, onHeartbeat) {
472
472
  return spanned(client, "text", maxTokens, () => withTimeout(
473
- client.generateText(prompt, maxTokens, temperature),
473
+ client.generateTextWithMetadata(prompt, maxTokens, temperature),
474
474
  `${client.type}:${client.modelId}`,
475
475
  onHeartbeat
476
476
  ));
@@ -1253,6 +1253,7 @@ var SEMANTIC_MATCH_CHARS = 240;
1253
1253
  function idLabel(resourceId, annotationId) {
1254
1254
  return `[${resourceId}${annotationId ? `/${annotationId}` : ""}]`;
1255
1255
  }
1256
+ var DEFAULT_MAX_TOKENS = 500;
1256
1257
  async function generateResourceFromTopic(topic, entityTypes, client, logger, userPrompt, locale, context, temperature, maxTokens, sourceLanguage, outputMediaType = "text/markdown", task = "resource", structure, cite = false, repair) {
1257
1258
  logger.debug("Generating resource from topic", {
1258
1259
  topicPreview: topic.substring(0, 100),
@@ -1268,7 +1269,7 @@ async function generateResourceFromTopic(topic, entityTypes, client, logger, use
1268
1269
  structure
1269
1270
  });
1270
1271
  const finalTemperature = temperature ?? 0.7;
1271
- const finalMaxTokens = maxTokens ?? 500;
1272
+ const finalMaxTokens = maxTokens ?? DEFAULT_MAX_TOKENS;
1272
1273
  const languageInstruction = locale && locale !== "en" ? `
1273
1274
 
1274
1275
  IMPORTANT: Write the entire resource in ${getLanguageName(locale)}.` : "";
@@ -1295,6 +1296,9 @@ The source resource and embedded context are in ${getLanguageName(sourceLanguage
1295
1296
  parts.push(`- ${label}: ${bodyItem.value}`);
1296
1297
  }
1297
1298
  }
1299
+ if (focus.userHint) {
1300
+ parts.push(`- User hint (steers what to generate): ${focus.userHint}`);
1301
+ }
1298
1302
  annotationSection = `
1299
1303
 
1300
1304
  Annotation context:
@@ -1453,16 +1457,16 @@ ${formatRequirements}`;
1453
1457
  temperature: finalTemperature,
1454
1458
  maxTokens: finalMaxTokens
1455
1459
  });
1456
- const response = await boundedGenerate(client, prompt, finalMaxTokens, finalTemperature);
1457
- logger.debug("Got response from inference", { responseLength: response.length });
1458
- const result = parseResponse(response);
1460
+ const response = await boundedGenerateWithMetadata(client, prompt, finalMaxTokens, finalTemperature);
1461
+ logger.debug("Got response from inference", { responseLength: response.text.length, stopReason: response.stopReason });
1462
+ const result = parseResponse(response.text);
1459
1463
  logger.debug("Parsed response", {
1460
1464
  hasTitle: !!result.title,
1461
1465
  titleLength: result.title?.length,
1462
1466
  hasContent: !!result.content,
1463
1467
  contentLength: result.content?.length
1464
1468
  });
1465
- return result;
1469
+ return { ...result, truncated: response.stopReason === "max_tokens" };
1466
1470
  }
1467
1471
  var PINNED_CREATION_TIMESTAMP = 17e8;
1468
1472
  var MAX_COMPILE_REPAIRS = 2;
@@ -1602,7 +1606,7 @@ async function processHighlightJob(content, inferenceClient, params, buildAnnota
1602
1606
  onProgress(100, { code: "complete-created", count: annotations.length, kind: "highlight" }, echo);
1603
1607
  return {
1604
1608
  annotations,
1605
- result: { highlightsFound: highlights.length, highlightsCreated: annotations.length }
1609
+ result: { kind: "highlight-annotation", highlightsFound: highlights.length, highlightsCreated: annotations.length }
1606
1610
  };
1607
1611
  }
1608
1612
  function detectionEcho(p) {
@@ -1642,7 +1646,7 @@ async function processCommentJob(content, inferenceClient, params, buildAnnotati
1642
1646
  onProgress(100, { code: "complete-created", count: annotations.length, kind: "comment" }, echo);
1643
1647
  return {
1644
1648
  annotations,
1645
- result: { commentsFound: comments.length, commentsCreated: annotations.length }
1649
+ result: { kind: "comment-annotation", commentsFound: comments.length, commentsCreated: annotations.length }
1646
1650
  };
1647
1651
  }
1648
1652
  async function processAssessmentJob(content, inferenceClient, params, buildAnnotation, onProgress) {
@@ -1682,7 +1686,7 @@ async function processAssessmentJob(content, inferenceClient, params, buildAnnot
1682
1686
  onProgress(100, { code: "complete-created", count: annotations.length, kind: "assessment" }, echo);
1683
1687
  return {
1684
1688
  annotations,
1685
- result: { assessmentsFound: assessments.length, assessmentsCreated: annotations.length }
1689
+ result: { kind: "assessment-annotation", assessmentsFound: assessments.length, assessmentsCreated: annotations.length }
1686
1690
  };
1687
1691
  }
1688
1692
  async function processReferenceJob(content, inferenceClient, params, buildAnnotation, onProgress, logger) {
@@ -1772,7 +1776,7 @@ async function processReferenceJob(content, inferenceClient, params, buildAnnota
1772
1776
  onProgress(100, { code: "complete-created", count: annotations.length, kind: "reference" }, { requestParams });
1773
1777
  return {
1774
1778
  annotations,
1775
- result: { totalFound, totalEmitted: annotations.length, errors }
1779
+ result: { kind: "reference-annotation", totalFound, totalEmitted: annotations.length, errors }
1776
1780
  };
1777
1781
  }
1778
1782
  async function processTagJob(content, inferenceClient, params, buildAnnotation, onProgress) {
@@ -1829,7 +1833,7 @@ async function processTagJob(content, inferenceClient, params, buildAnnotation,
1829
1833
  onProgress(100, { code: "complete-created", count: annotations.length, kind: "tag" });
1830
1834
  return {
1831
1835
  annotations,
1832
- result: { tagsFound: tags.length, tagsCreated: annotations.length, byCategory }
1836
+ result: { kind: "tag-annotation", tagsFound: tags.length, tagsCreated: annotations.length, byCategory }
1833
1837
  };
1834
1838
  }
1835
1839
  function assertWithinOutputBudget(byteLength) {
@@ -1876,7 +1880,17 @@ async function processGenerationJob(inferenceClient, params, onProgress, logger)
1876
1880
  }
1877
1881
  let compiled = compileTypst(source);
1878
1882
  let repairs = 0;
1879
- while ("error" in compiled && repairs < MAX_COMPILE_REPAIRS) {
1883
+ while ("error" in compiled) {
1884
+ if (generated2.truncated) {
1885
+ throw new Error(
1886
+ `Generation stopped at the maxTokens ceiling (${params.maxTokens ?? DEFAULT_MAX_TOKENS} tokens) and the cut-off Typst source does not compile \u2014 repair cannot help; raise maxTokens. Compile error: ${compiled.error}`
1887
+ );
1888
+ }
1889
+ if (repairs >= MAX_COMPILE_REPAIRS) {
1890
+ throw new Error(
1891
+ `Typst compilation failed after ${MAX_COMPILE_REPAIRS} repair attempts: ${compiled.error}`
1892
+ );
1893
+ }
1880
1894
  repairs++;
1881
1895
  logger.warn("Typst compile failed \u2014 feeding the error back for repair", {
1882
1896
  attempt: repairs,
@@ -1908,21 +1922,19 @@ async function processGenerationJob(inferenceClient, params, onProgress, logger)
1908
1922
  }
1909
1923
  compiled = compileTypst(source);
1910
1924
  }
1911
- if ("error" in compiled) {
1912
- throw new Error(
1913
- `Typst compilation failed after ${MAX_COMPILE_REPAIRS} repair attempts: ${compiled.error}`
1914
- );
1915
- }
1916
1925
  assertWithinOutputBudget(compiled.pdf.byteLength);
1917
1926
  onProgress(95, { code: "creating-resource" });
1927
+ onProgress(100, { code: "complete-generated", truncated: generated2.truncated });
1918
1928
  return {
1919
1929
  content: compiled.pdf,
1920
- title: generated2.title ?? title,
1930
+ title,
1921
1931
  format: outputMediaType,
1922
1932
  citations: citations2,
1923
1933
  result: {
1934
+ kind: "generation",
1924
1935
  resourceId: "",
1925
- resourceName: generated2.title ?? title
1936
+ resourceName: title,
1937
+ truncated: generated2.truncated
1926
1938
  }
1927
1939
  };
1928
1940
  }
@@ -1953,14 +1965,17 @@ async function processGenerationJob(inferenceClient, params, onProgress, logger)
1953
1965
  onProgress(95, { code: "creating-resource" });
1954
1966
  const artifact = new TextEncoder().encode(content);
1955
1967
  assertWithinOutputBudget(artifact.byteLength);
1968
+ onProgress(100, { code: "complete-generated", truncated: generated.truncated });
1956
1969
  return {
1957
1970
  content: artifact,
1958
- title: generated.title ?? title,
1971
+ title,
1959
1972
  format: outputMediaType,
1960
1973
  citations,
1961
1974
  result: {
1975
+ kind: "generation",
1962
1976
  resourceId: "",
1963
- resourceName: generated.title ?? title
1977
+ resourceName: title,
1978
+ truncated: generated.truncated
1964
1979
  }
1965
1980
  };
1966
1981
  }