@semiont/jobs 0.5.12 → 0.5.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.
package/dist/index.d.ts CHANGED
@@ -691,5 +691,37 @@ declare function generateResourceFromTopic(topic: string, entityTypes: string[],
691
691
  content: string;
692
692
  }>;
693
693
 
694
- export { AnnotationDetection, FsJobQueue, generateResourceFromTopic, isCancelledJob, isCompleteJob, isFailedJob, isPendingJob, isRunningJob, processAssessmentJob, processCommentJob, processGenerationJob, processHighlightJob, processReferenceJob, processTagJob };
694
+ /**
695
+ * Worker Runtime — the importable half of the worker host.
696
+ *
697
+ * `worker-main.ts` is a process entrypoint (config at module scope,
698
+ * `main()` at import) and therefore untestable by construction; everything
699
+ * a unit test needs to reach lives here instead, fully parameterized — no
700
+ * module-scope env reads, no side effects at import.
701
+ *
702
+ * The load-bearing contract this module owns (and the reason it was
703
+ * extracted): a worker's stamped identity is the DID the
704
+ * `/api/tokens/agent` exchange MINTED for it, carried verbatim — never
705
+ * re-derived from the URL the worker happens to dial. One logical agent
706
+ * previously got two DIDs that way (.plans/bugs/agent-did-host-skew.md).
707
+ */
708
+
709
+ /**
710
+ * Stall watchdog (WORKER-LIVENESS.md P3) — the fail-fast line behind the
711
+ * inference timeout. There is no poll loop to heartbeat; the honest
712
+ * stall signal in this push-driven architecture is *processing without
713
+ * activity*: an agent holding a claimed job whose `lastActivityAt`
714
+ * (claim / progress / finish) has stopped advancing is wedged — the
715
+ * adapter ignores every announcement while `isProcessing`, so a wedged
716
+ * agent never recovers on its own. Silent hang → loud crash → whatever
717
+ * restart policy the deployment chose.
718
+ *
719
+ * Thresholds are fixed by design (no env knobs) and deliberately
720
+ * layered: inference timeout (10 min, P2) fires first; this watchdog
721
+ * (15 min) catches the failure modes nobody predicted; the backend's
722
+ * dead-worker janitor (30 min) re-queues the job regardless.
723
+ */
724
+ declare const STALL_THRESHOLD_MS: number;
725
+
726
+ export { AnnotationDetection, FsJobQueue, STALL_THRESHOLD_MS, generateResourceFromTopic, isCancelledJob, isCompleteJob, isFailedJob, isPendingJob, isRunningJob, processAssessmentJob, processCommentJob, processGenerationJob, processHighlightJob, processReferenceJob, processTagJob };
695
727
  export type { AnyJob, AssessmentDetectionJob, AssessmentDetectionParams, AssessmentDetectionProgress, AssessmentDetectionResult, CancelledJob, CommentDetectionJob, CommentDetectionParams, CommentDetectionProgress, CommentDetectionResult, CompleteJob, DetectionJob, DetectionParams, DetectionProgress, DetectionResult, FailedJob, GenerationJob, GenerationParams, GenerationResult, HighlightDetectionJob, HighlightDetectionParams, HighlightDetectionProgress, HighlightDetectionResult, JobMetadata, JobQueryFilters, JobQueue, JobStatus, JobType, OnProgress, PendingJob, ProcessorResult, RunningJob, TagDetectionJob, TagDetectionParams, TagDetectionProgress, TagDetectionResult, YieldProgress };
package/dist/index.js CHANGED
@@ -2,6 +2,10 @@ import { promises } from 'fs';
2
2
  import * as path from 'path';
3
3
  import { jobId, deriveViews, reconcileSelector, isObject, isString, getLocaleEnglishName, didToAgent, isArray } from '@semiont/core';
4
4
  import { generateAnnotationId } from '@semiont/event-sourcing';
5
+ import '@semiont/content';
6
+ import '@semiont/observability';
7
+ import '@semiont/sdk';
8
+ import '@semiont/http-transport';
5
9
 
6
10
  // src/fs-job-queue.ts
7
11
  var REANNOUNCE_INTERVAL_MS = 3e4;
@@ -419,6 +423,41 @@ function isFailedJob(job) {
419
423
  function isCancelledJob(job) {
420
424
  return job.status === "cancelled";
421
425
  }
426
+
427
+ // src/workers/inference-call.ts
428
+ var INFERENCE_TIMEOUT_MS = 10 * 6e4;
429
+ async function withTimeout(work, label) {
430
+ let timer;
431
+ const timedOut = new Promise((_, reject) => {
432
+ timer = setTimeout(() => {
433
+ reject(new Error(
434
+ `Inference call timed out after ${INFERENCE_TIMEOUT_MS / 6e4} minutes (${label}) \u2014 failing the job to keep the claim loop live`
435
+ ));
436
+ }, INFERENCE_TIMEOUT_MS);
437
+ timer.unref?.();
438
+ });
439
+ try {
440
+ return await Promise.race([work, timedOut]);
441
+ } catch (err) {
442
+ work.catch(() => {
443
+ });
444
+ throw err;
445
+ } finally {
446
+ clearTimeout(timer);
447
+ }
448
+ }
449
+ function boundedGenerate(client, prompt, maxTokens, temperature, options) {
450
+ return withTimeout(
451
+ client.generateText(prompt, maxTokens, temperature, options),
452
+ `${client.type}:${client.modelId}`
453
+ );
454
+ }
455
+ function boundedGenerateWithMetadata(client, prompt, maxTokens, temperature, options) {
456
+ return withTimeout(
457
+ client.generateTextWithMetadata(prompt, maxTokens, temperature, options),
458
+ `${client.type}:${client.modelId}`
459
+ );
460
+ }
422
461
  function languageName(tag) {
423
462
  return getLocaleEnglishName(tag) || tag;
424
463
  }
@@ -900,7 +939,7 @@ var AnnotationDetection = class {
900
939
  */
901
940
  static async detectComments(content, client, instructions, tone, density, language, sourceLanguage) {
902
941
  const prompt = MotivationPrompts.buildCommentPrompt(content, instructions, tone, density, language, sourceLanguage);
903
- const response = await client.generateTextWithMetadata(prompt, 3e3, 0.4, { format: "json" });
942
+ const response = await boundedGenerateWithMetadata(client, prompt, 3e3, 0.4, { format: "json" });
904
943
  assertNotTruncated(response, "comment");
905
944
  return MotivationParsers.parseComments(response.text, content);
906
945
  }
@@ -913,7 +952,7 @@ var AnnotationDetection = class {
913
952
  */
914
953
  static async detectHighlights(content, client, instructions, density, sourceLanguage) {
915
954
  const prompt = MotivationPrompts.buildHighlightPrompt(content, instructions, density, sourceLanguage);
916
- const response = await client.generateTextWithMetadata(prompt, 2e3, 0.3, { format: "json" });
955
+ const response = await boundedGenerateWithMetadata(client, prompt, 2e3, 0.3, { format: "json" });
917
956
  assertNotTruncated(response, "highlight");
918
957
  return MotivationParsers.parseHighlights(response.text, content);
919
958
  }
@@ -926,7 +965,7 @@ var AnnotationDetection = class {
926
965
  */
927
966
  static async detectAssessments(content, client, instructions, tone, density, language, sourceLanguage) {
928
967
  const prompt = MotivationPrompts.buildAssessmentPrompt(content, instructions, tone, density, language, sourceLanguage);
929
- const response = await client.generateTextWithMetadata(prompt, 3e3, 0.3, { format: "json" });
968
+ const response = await boundedGenerateWithMetadata(client, prompt, 3e3, 0.3, { format: "json" });
930
969
  assertNotTruncated(response, "assessment");
931
970
  return MotivationParsers.parseAssessments(response.text, content);
932
971
  }
@@ -957,7 +996,7 @@ var AnnotationDetection = class {
957
996
  categoryInfo.examples,
958
997
  sourceLanguage
959
998
  );
960
- const response = await client.generateTextWithMetadata(prompt, 4e3, 0.2, { format: "json" });
999
+ const response = await boundedGenerateWithMetadata(client, prompt, 4e3, 0.2, { format: "json" });
961
1000
  assertNotTruncated(response, "tag");
962
1001
  const parsedTags = MotivationParsers.parseTags(response.text);
963
1002
  return MotivationParsers.validateTagOffsets(parsedTags, content, category);
@@ -1013,7 +1052,8 @@ If no entities are found, respond with an empty array [].
1013
1052
  Example output:
1014
1053
  [{"exact":"Alice","entityType":"Person","prefix":"","suffix":" went to"},{"exact":"Paris","entityType":"Location","prefix":"went to ","suffix":" yesterday"}]`;
1015
1054
  logger.debug("Sending entity extraction request", { entityTypes: entityTypesDescription });
1016
- const response = await client.generateTextWithMetadata(
1055
+ const response = await boundedGenerateWithMetadata(
1056
+ client,
1017
1057
  prompt,
1018
1058
  4e3,
1019
1059
  // Increased to handle many entities without truncation
@@ -1263,7 +1303,7 @@ ${formatRequirements}`;
1263
1303
  temperature: finalTemperature,
1264
1304
  maxTokens: finalMaxTokens
1265
1305
  });
1266
- const response = await client.generateText(prompt, finalMaxTokens, finalTemperature);
1306
+ const response = await boundedGenerate(client, prompt, finalMaxTokens, finalTemperature);
1267
1307
  logger.debug("Got response from inference", { responseLength: response.length });
1268
1308
  const result = parseResponse(response);
1269
1309
  logger.debug("Parsed response", {
@@ -1661,7 +1701,8 @@ async function processGenerationJob(inferenceClient, params, onProgress, logger)
1661
1701
  }
1662
1702
  };
1663
1703
  }
1704
+ var STALL_THRESHOLD_MS = 15 * 6e4;
1664
1705
 
1665
- export { AnnotationDetection, FsJobQueue, generateResourceFromTopic, isCancelledJob, isCompleteJob, isFailedJob, isPendingJob, isRunningJob, processAssessmentJob, processCommentJob, processGenerationJob, processHighlightJob, processReferenceJob, processTagJob };
1706
+ export { AnnotationDetection, FsJobQueue, STALL_THRESHOLD_MS, generateResourceFromTopic, isCancelledJob, isCompleteJob, isFailedJob, isPendingJob, isRunningJob, processAssessmentJob, processCommentJob, processGenerationJob, processHighlightJob, processReferenceJob, processTagJob };
1666
1707
  //# sourceMappingURL=index.js.map
1667
1708
  //# sourceMappingURL=index.js.map