@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.
- package/infrastructure/schema/09_queue_job.surql +38 -0
- package/infrastructure/schema/10_autonomous_job.surql +44 -0
- package/package.json +2 -2
- package/src/ai-gateway/ai-gateway.ts +130 -21
- package/src/ai-gateway/cache-headers.ts +26 -1
- package/src/create-runtime.ts +10 -1
- package/src/db/base.service.ts +6 -1
- package/src/db/tables.ts +4 -0
- package/src/queues/autonomous-job.queue.ts +134 -0
- package/src/queues/document-processor.queue.ts +13 -2
- package/src/queues/index.ts +1 -0
- package/src/queues/memory-consolidation.queue.ts +22 -3
- package/src/queues/queue-factory.ts +33 -4
- package/src/runtime/chat-run-registry.ts +4 -0
- package/src/runtime/context-compaction.ts +100 -12
- package/src/runtime/memory-prompts-fact.ts +3 -1
- package/src/runtime/runtime-config.ts +1 -1
- package/src/runtime/runtime-worker-registry.ts +3 -0
- package/src/services/autonomous-job.service.ts +692 -0
- package/src/services/index.ts +2 -0
- package/src/services/plan-deadline.service.ts +6 -4
- package/src/services/queue-job.service.ts +356 -0
- package/src/services/workstream-message.service.ts +25 -14
- package/src/services/workstream-title.service.ts +1 -1
- package/src/services/workstream-turn-preparation.service.ts +22 -6
- package/src/services/workstream-turn.ts +11 -3
- package/src/services/workstream.service.ts +19 -2
- package/src/system-agents/context-compaction.agent.ts +2 -2
- package/src/system-agents/delegated-agent-factory.ts +2 -9
- package/src/system-agents/memory-reranker.agent.ts +2 -2
- package/src/system-agents/memory.agent.ts +2 -2
- package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
- package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
- package/src/system-agents/skill-extractor.agent.ts +2 -2
- package/src/system-agents/skill-manager.agent.ts +2 -2
- package/src/system-agents/title-generator.agent.ts +2 -2
- package/src/tools/research-topic.tool.ts +2 -2
- package/src/utils/date-time.ts +11 -0
- package/src/workers/utils/file-section-chunker.ts +1 -1
- 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<
|
|
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
|
-
|
|
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
|
|
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) =>
|
|
609
|
-
.filter((question): question is
|
|
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
|
-
|
|
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) => ({
|
|
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) => ({
|
|
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
|
|
665
|
-
|
|
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()
|
|
43
|
+
Today's date is ${formatUtcPromptDate(new Date())}.
|
|
42
44
|
${baseInstructions}`
|
|
43
45
|
|
|
44
46
|
const userPrompt = `Conversation:\n${parsedMessages}`
|
|
@@ -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,
|