@lota-sdk/core 0.1.18 → 0.1.20

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.
Files changed (40) hide show
  1. package/infrastructure/schema/09_queue_job.surql +38 -0
  2. package/infrastructure/schema/10_autonomous_job.surql +44 -0
  3. package/package.json +2 -2
  4. package/src/ai-gateway/ai-gateway.ts +130 -21
  5. package/src/ai-gateway/cache-headers.ts +26 -1
  6. package/src/create-runtime.ts +10 -1
  7. package/src/db/base.service.ts +6 -1
  8. package/src/db/tables.ts +4 -0
  9. package/src/queues/autonomous-job.queue.ts +134 -0
  10. package/src/queues/document-processor.queue.ts +13 -2
  11. package/src/queues/index.ts +1 -0
  12. package/src/queues/memory-consolidation.queue.ts +22 -3
  13. package/src/queues/queue-factory.ts +33 -4
  14. package/src/runtime/chat-run-registry.ts +4 -0
  15. package/src/runtime/context-compaction.ts +100 -12
  16. package/src/runtime/memory-prompts-fact.ts +3 -1
  17. package/src/runtime/runtime-config.ts +1 -1
  18. package/src/runtime/runtime-worker-registry.ts +3 -0
  19. package/src/services/autonomous-job.service.ts +692 -0
  20. package/src/services/index.ts +2 -0
  21. package/src/services/plan-deadline.service.ts +6 -4
  22. package/src/services/queue-job.service.ts +356 -0
  23. package/src/services/workstream-message.service.ts +25 -14
  24. package/src/services/workstream-title.service.ts +1 -1
  25. package/src/services/workstream-turn-preparation.service.ts +22 -6
  26. package/src/services/workstream-turn.ts +11 -3
  27. package/src/services/workstream.service.ts +19 -2
  28. package/src/system-agents/context-compaction.agent.ts +2 -2
  29. package/src/system-agents/delegated-agent-factory.ts +2 -9
  30. package/src/system-agents/memory-reranker.agent.ts +2 -2
  31. package/src/system-agents/memory.agent.ts +2 -2
  32. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
  33. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
  34. package/src/system-agents/skill-extractor.agent.ts +2 -2
  35. package/src/system-agents/skill-manager.agent.ts +2 -2
  36. package/src/system-agents/title-generator.agent.ts +2 -2
  37. package/src/tools/research-topic.tool.ts +2 -2
  38. package/src/utils/date-time.ts +11 -0
  39. package/src/workers/utils/file-section-chunker.ts +1 -1
  40. package/src/workers/worker-utils.ts +35 -7
@@ -4,6 +4,7 @@ import type IORedis from 'ioredis'
4
4
 
5
5
  import { serverLogger } from '../config/logger'
6
6
  import { getRedisConnectionForBullMQ } from '../redis'
7
+ import { queueJobService } from '../services/queue-job.service'
7
8
  import {
8
9
  attachWorkerEvents,
9
10
  createTracedWorkerProcessor,
@@ -26,7 +27,7 @@ interface QueueFactoryConfigBase {
26
27
  }
27
28
 
28
29
  interface QueueFactoryConfigInline<TJob> extends QueueFactoryConfigBase {
29
- processor: (job: Job<TJob>) => Promise<void>
30
+ processor: (job: Job<TJob>) => Promise<unknown>
30
31
  processorPath?: never
31
32
  }
32
33
 
@@ -45,15 +46,34 @@ export interface QueueFactory<TJob> {
45
46
 
46
47
  export function createQueueFactory<TJob>(config: QueueFactoryConfig<TJob>): QueueFactory<TJob> {
47
48
  let _queue: Queue<TJob, unknown, string> | null = null
49
+ let _queueConnection: IORedis | null = null
48
50
 
49
51
  const getConnection = () => config.connectionProvider?.() ?? getRedisConnectionForBullMQ()
50
52
 
51
53
  const getQueue = (): Queue<TJob, unknown, string> => {
52
- if (!_queue) {
54
+ const connection = getConnection()
55
+ const shouldRecreateQueue =
56
+ _queue === null ||
57
+ _queueConnection === null ||
58
+ _queueConnection !== connection ||
59
+ _queueConnection.status === 'close' ||
60
+ _queueConnection.status === 'end'
61
+
62
+ if (shouldRecreateQueue) {
63
+ if (_queue) {
64
+ void _queue.close().catch((error: unknown) => {
65
+ serverLogger.warn`Failed to close stale ${config.displayName} queue: ${error}`
66
+ })
67
+ }
68
+
53
69
  _queue = new Queue<TJob, unknown, string>(config.name, {
54
- connection: getConnection(),
70
+ connection,
55
71
  defaultJobOptions: { ...DEFAULT_JOB_RETENTION, ...config.defaultJobOptions },
56
72
  })
73
+ _queueConnection = connection
74
+ }
75
+ if (_queue === null) {
76
+ throw new Error(`Failed to initialize queue: ${config.name}`)
57
77
  }
58
78
  return _queue
59
79
  }
@@ -63,7 +83,16 @@ export function createQueueFactory<TJob>(config: QueueFactoryConfig<TJob>): Queu
63
83
  const toData = (job: TJob) => job as Parameters<QueueAdd>[1]
64
84
 
65
85
  const enqueue = async (job: TJob, options?: JobsOptions): Promise<void> => {
66
- await getQueue().add(jobName, toData(job), options)
86
+ const queuedJob = await getQueue().add(jobName, toData(job), options)
87
+ await queueJobService.recordEnqueued({
88
+ queueName: config.name,
89
+ id: queuedJob.id,
90
+ name: queuedJob.name,
91
+ data: queuedJob.data,
92
+ opts: queuedJob.opts,
93
+ attemptsMade: queuedJob.attemptsMade,
94
+ timestamp: queuedJob.timestamp,
95
+ })
67
96
  }
68
97
 
69
98
  const startWorker = (options: { registerSignals?: boolean } = {}): WorkerHandle => {
@@ -1,6 +1,10 @@
1
1
  export class ChatRunRegistry {
2
2
  private controllers = new Map<string, AbortController>()
3
3
 
4
+ has(runId: string): boolean {
5
+ return this.controllers.has(runId)
6
+ }
7
+
4
8
  register(runId: string, controller: AbortController): void {
5
9
  this.controllers.set(runId, controller)
6
10
  }
@@ -101,6 +101,90 @@ function sanitizeStateText(value: string): string | null {
101
101
  return normalized
102
102
  }
103
103
 
104
+ function buildExistingWorkstreamStateForCompactionPrompt(state: WorkstreamState): Record<string, unknown> {
105
+ const currentPlanText = state.currentPlan ? sanitizeStateText(state.currentPlan.text) : null
106
+
107
+ const activeConstraints = state.activeConstraints
108
+ .map((constraint) => ({
109
+ id: constraint.id,
110
+ text: sanitizeStateText(constraint.text),
111
+ source: constraint.source,
112
+ approved: constraint.approved,
113
+ sourceMessageIds: constraint.sourceMessageIds,
114
+ }))
115
+ .filter((constraint): constraint is typeof constraint & { text: string } => Boolean(constraint.text))
116
+
117
+ const keyDecisions = state.keyDecisions
118
+ .map((decision) => ({
119
+ id: decision.id,
120
+ decision: sanitizeStateText(decision.decision),
121
+ rationale: sanitizeStateText(decision.rationale),
122
+ agent: decision.agent,
123
+ sourceMessageIds: decision.sourceMessageIds,
124
+ confidence: decision.confidence,
125
+ }))
126
+ .filter((decision): decision is typeof decision & { decision: string; rationale: string } =>
127
+ Boolean(decision.decision && decision.rationale),
128
+ )
129
+
130
+ const tasks = state.tasks
131
+ .map((task) => ({
132
+ id: task.id,
133
+ title: sanitizeStateText(task.title),
134
+ status: task.status,
135
+ owner: sanitizeStateText(task.owner),
136
+ externalId: task.externalId,
137
+ source: task.source,
138
+ sourceMessageIds: task.sourceMessageIds,
139
+ }))
140
+ .filter((task): task is typeof task & { title: string; owner: string } => Boolean(task.title && task.owner))
141
+
142
+ const openQuestions = state.openQuestions
143
+ .map((question) => ({
144
+ id: question.id,
145
+ text: sanitizeStateText(question.text),
146
+ source: question.source,
147
+ sourceMessageIds: question.sourceMessageIds,
148
+ }))
149
+ .filter((question): question is typeof question & { text: string } => Boolean(question.text))
150
+
151
+ const artifacts = state.artifacts
152
+ .map((artifact) => ({
153
+ id: artifact.id,
154
+ name: sanitizeStateText(artifact.name),
155
+ type: artifact.type,
156
+ pointer: sanitizeStateText(artifact.pointer),
157
+ }))
158
+ .filter((artifact): artifact is typeof artifact & { name: string; pointer: string } =>
159
+ Boolean(artifact.name && artifact.pointer),
160
+ )
161
+
162
+ const agentContributions = state.agentContributions
163
+ .map((note) => ({ id: note.id, agent: note.agent, summary: sanitizeStateText(note.summary) }))
164
+ .filter((note): note is typeof note & { summary: string } => Boolean(note.summary))
165
+
166
+ return {
167
+ currentPlan: currentPlanText
168
+ ? {
169
+ id: state.currentPlan?.id,
170
+ text: currentPlanText,
171
+ source: state.currentPlan?.source,
172
+ approved: state.currentPlan?.approved,
173
+ sourceMessageIds: state.currentPlan?.sourceMessageIds ?? [],
174
+ }
175
+ : null,
176
+ activeConstraints,
177
+ keyDecisions,
178
+ tasks,
179
+ openQuestions,
180
+ risks: state.risks.map((risk) => sanitizeStateText(risk)).filter((risk): risk is string => Boolean(risk)),
181
+ artifacts,
182
+ agentContributions,
183
+ approvedBy: state.approvedBy ? sanitizeStateText(state.approvedBy) : undefined,
184
+ approvalNote: state.approvalNote ? sanitizeStateText(state.approvalNote) : undefined,
185
+ }
186
+ }
187
+
104
188
  function createStableId(prefix: string, ...parts: Array<string | number | undefined>): string {
105
189
  const payload = parts
106
190
  .map((part) => (part === undefined ? '' : String(part)))
@@ -488,7 +572,7 @@ export function buildContextCompactionPrompt(params: ContextCompactionPromptPara
488
572
  params.previousSummary.trim() || 'None',
489
573
  '</previous-summary>',
490
574
  '<existing-workstream-state>',
491
- JSON.stringify(params.existingState),
575
+ JSON.stringify(buildExistingWorkstreamStateForCompactionPrompt(params.existingState)),
492
576
  '</existing-workstream-state>',
493
577
  '<new-messages>',
494
578
  params.transcript || 'None',
@@ -605,27 +689,34 @@ export function createContextCompactionRuntime(
605
689
  .filter((constraint): constraint is typeof constraint & { text: string } => Boolean(constraint.text))
606
690
 
607
691
  const openQuestions = state.openQuestions
608
- .map((question) => ({ ...question, text: sanitizeStateText(question.text) }))
609
- .filter((question): question is typeof question & { text: string } => Boolean(question.text))
692
+ .map((question) => sanitizeStateText(question.text))
693
+ .filter((question): question is string => Boolean(question))
610
694
 
611
695
  const decisions = state.keyDecisions
612
696
  .map((decision) => ({
613
- ...decision,
697
+ agent: decision.agent,
614
698
  decision: sanitizeStateText(decision.decision),
615
699
  rationale: sanitizeStateText(decision.rationale),
700
+ confidence: decision.confidence,
616
701
  }))
617
702
  .filter((decision): decision is typeof decision & { decision: string; rationale: string } =>
618
703
  Boolean(decision.decision && decision.rationale),
619
704
  )
620
705
 
621
706
  const tasks = state.tasks
622
- .map((task) => ({ ...task, title: sanitizeStateText(task.title), owner: sanitizeStateText(task.owner) }))
707
+ .map((task) => ({
708
+ title: sanitizeStateText(task.title),
709
+ status: task.status,
710
+ owner: sanitizeStateText(task.owner),
711
+ externalId: task.externalId,
712
+ source: task.source,
713
+ }))
623
714
  .filter((task): task is typeof task & { title: string; owner: string } => Boolean(task.title && task.owner))
624
715
 
625
716
  const artifacts = state.artifacts
626
717
  .map((artifact) => ({
627
- ...artifact,
628
718
  name: sanitizeStateText(artifact.name),
719
+ type: artifact.type,
629
720
  pointer: sanitizeStateText(artifact.pointer),
630
721
  }))
631
722
  .filter((artifact): artifact is typeof artifact & { name: string; pointer: string } =>
@@ -633,7 +724,7 @@ export function createContextCompactionRuntime(
633
724
  )
634
725
 
635
726
  const agentContributions = state.agentContributions
636
- .map((note) => ({ ...note, summary: sanitizeStateText(note.summary) }))
727
+ .map((note) => ({ agent: note.agent, summary: sanitizeStateText(note.summary) }))
637
728
  .filter((note): note is typeof note & { summary: string } => Boolean(note.summary))
638
729
 
639
730
  const payload = {
@@ -661,12 +752,9 @@ export function createContextCompactionRuntime(
661
752
  artifacts,
662
753
  agentContributions,
663
754
  advisory: {
664
- approvedBy: state.approvedBy ?? null,
665
- approvedAt: state.approvedAt ?? null,
666
- approvalMessageId: state.approvalMessageId ?? null,
667
- approvalNote: state.approvalNote ?? null,
755
+ approvedBy: state.approvedBy ? sanitizeStateText(state.approvedBy) : null,
756
+ approvalNote: state.approvalNote ? sanitizeStateText(state.approvalNote) : null,
668
757
  },
669
- lastUpdated: state.lastUpdated,
670
758
  }
671
759
 
672
760
  return ['<workstream-state>', JSON.stringify(payload, null, 2), '</workstream-state>'].join('\n')
@@ -1,3 +1,5 @@
1
+ import { formatUtcPromptDate } from '../utils/date-time'
2
+
1
3
  export function getFactRetrievalMessages(
2
4
  parsedMessages: string,
3
5
  customPrompt?: string,
@@ -38,7 +40,7 @@ Hard rules:
38
40
  - Prefer returning fewer items. If uncertain, return an empty list.
39
41
  - Max ${maxFacts} facts.
40
42
 
41
- Today's date is ${new Date().toISOString().split('T')[0]}.
43
+ Today's date is ${formatUtcPromptDate(new Date())}.
42
44
  ${baseInstructions}`
43
45
 
44
46
  const userPrompt = `Conversation:\n${parsedMessages}`
@@ -292,7 +292,7 @@ export const LOTA_RUNTIME_ENV_KEYS = Object.freeze([
292
292
  'SURREALDB_PASSWORD',
293
293
  'REDIS_URL',
294
294
  'AI_GATEWAY_URL',
295
- 'LOTA_KEY',
295
+ 'AI_GATEWAY_KEY',
296
296
  'AI_EMBEDDING_MODEL',
297
297
  'S3_ENDPOINT',
298
298
  'S3_BUCKET',
@@ -1,3 +1,4 @@
1
+ import { startAutonomousJobWorker } from '../queues/autonomous-job.queue'
1
2
  import { startContextCompactionWorker } from '../queues/context-compaction.queue'
2
3
  import { startDelayedNodePromotionWorker } from '../queues/delayed-node-promotion.queue'
3
4
  import { scheduleRecurringConsolidation, startMemoryConsolidationWorker } from '../queues/memory-consolidation.queue'
@@ -9,6 +10,7 @@ import { startSkillExtractionWorker } from '../queues/skill-extraction.queue'
9
10
  import { startWorkstreamTitleGenerationWorker } from '../queues/workstream-title-generation.queue'
10
11
 
11
12
  export interface LotaRuntimeWorkerStartRegistry {
13
+ autonomousJob: typeof startAutonomousJobWorker
12
14
  contextCompaction: typeof startContextCompactionWorker
13
15
  delayedNodePromotion: typeof startDelayedNodePromotionWorker
14
16
  memoryConsolidation: typeof startMemoryConsolidationWorker
@@ -37,6 +39,7 @@ export interface LotaRuntimeWorkerExtensions {
37
39
  export function buildRuntimeWorkerRegistry(extraWorkers?: LotaRuntimeWorkerExtensions): LotaRuntimeWorkers {
38
40
  return {
39
41
  start: {
42
+ autonomousJob: startAutonomousJobWorker,
40
43
  contextCompaction: startContextCompactionWorker,
41
44
  delayedNodePromotion: startDelayedNodePromotionWorker,
42
45
  memoryConsolidation: startMemoryConsolidationWorker,