@lota-sdk/core 0.4.0 → 0.4.2

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.
@@ -146,6 +146,34 @@ DEFINE INDEX IF NOT EXISTS planNodeAttemptRunIdx ON TABLE planNodeAttempt COLUMN
146
146
  DEFINE INDEX IF NOT EXISTS planNodeAttemptNodeRunIdx ON TABLE planNodeAttempt COLUMNS nodeRunId;
147
147
  DEFINE INDEX IF NOT EXISTS planNodeAttemptRunNodeIdx ON TABLE planNodeAttempt COLUMNS runId, nodeId;
148
148
 
149
+ DEFINE TABLE IF NOT EXISTS artifact SCHEMAFULL;
150
+ DEFINE FIELD IF NOT EXISTS organizationId ON TABLE artifact TYPE record<organization>;
151
+ DEFINE FIELD IF NOT EXISTS authorAgentId ON TABLE artifact TYPE string;
152
+ DEFINE FIELD IF NOT EXISTS title ON TABLE artifact TYPE string;
153
+ DEFINE FIELD IF NOT EXISTS artifactKind ON TABLE artifact TYPE string;
154
+ DEFINE FIELD IF NOT EXISTS templateId ON TABLE artifact TYPE string;
155
+ DEFINE FIELD IF NOT EXISTS canonicalKey ON TABLE artifact TYPE string;
156
+ DEFINE FIELD IF NOT EXISTS version ON TABLE artifact TYPE int;
157
+ DEFINE FIELD IF NOT EXISTS status ON TABLE artifact TYPE string;
158
+ DEFINE FIELD IF NOT EXISTS supersededBy ON TABLE artifact TYPE option<record<artifact>>;
159
+ DEFINE FIELD IF NOT EXISTS storageKey ON TABLE artifact TYPE string;
160
+ DEFINE FIELD IF NOT EXISTS description ON TABLE artifact TYPE option<string>;
161
+ DEFINE FIELD IF NOT EXISTS tags ON TABLE artifact TYPE array DEFAULT [];
162
+ DEFINE FIELD IF NOT EXISTS references ON TABLE artifact TYPE array DEFAULT [];
163
+ DEFINE FIELD IF NOT EXISTS references.* ON TABLE artifact TYPE object FLEXIBLE;
164
+ DEFINE FIELD IF NOT EXISTS references.*.uri ON TABLE artifact TYPE string;
165
+ DEFINE FIELD IF NOT EXISTS references.*.targetType ON TABLE artifact TYPE string;
166
+ DEFINE FIELD IF NOT EXISTS references.*.targetId ON TABLE artifact TYPE string;
167
+ DEFINE FIELD IF NOT EXISTS sourceThreadId ON TABLE artifact TYPE option<record<thread>>;
168
+ DEFINE FIELD IF NOT EXISTS sourcePlanRunId ON TABLE artifact TYPE option<record<planRun>>;
169
+ DEFINE FIELD IF NOT EXISTS sourcePlanNodeId ON TABLE artifact TYPE option<string>;
170
+ DEFINE FIELD IF NOT EXISTS createdAt ON TABLE artifact TYPE datetime DEFAULT time::now() READONLY;
171
+ DEFINE FIELD IF NOT EXISTS updatedAt ON TABLE artifact TYPE datetime VALUE time::now();
172
+
173
+ DEFINE INDEX IF NOT EXISTS artifactOrgCanonicalVersionIdx ON TABLE artifact COLUMNS organizationId, canonicalKey, version UNIQUE;
174
+ DEFINE INDEX IF NOT EXISTS artifactOrgCanonicalStatusIdx ON TABLE artifact COLUMNS organizationId, canonicalKey, status;
175
+ DEFINE INDEX IF NOT EXISTS artifactOrgStatusUpdatedIdx ON TABLE artifact COLUMNS organizationId, status, updatedAt;
176
+
149
177
  DEFINE TABLE IF NOT EXISTS planArtifact SCHEMAFULL;
150
178
  DEFINE FIELD IF NOT EXISTS runId ON TABLE planArtifact TYPE record<planRun> REFERENCE ON DELETE CASCADE;
151
179
  DEFINE FIELD IF NOT EXISTS nodeId ON TABLE planArtifact TYPE string;
@@ -155,7 +183,9 @@ DEFINE FIELD IF NOT EXISTS kind ON TABLE planArtifact TYPE string;
155
183
  DEFINE FIELD IF NOT EXISTS pointer ON TABLE planArtifact TYPE string;
156
184
  DEFINE FIELD IF NOT EXISTS schemaRef ON TABLE planArtifact TYPE option<string>;
157
185
  DEFINE FIELD IF NOT EXISTS description ON TABLE planArtifact TYPE option<string>;
186
+ DEFINE FIELD IF NOT EXISTS content ON TABLE planArtifact TYPE option<string>;
158
187
  DEFINE FIELD IF NOT EXISTS payload ON TABLE planArtifact TYPE option<object> FLEXIBLE;
188
+ DEFINE FIELD IF NOT EXISTS publishedArtifactId ON TABLE planArtifact TYPE option<record<artifact>>;
159
189
  DEFINE FIELD IF NOT EXISTS createdAt ON TABLE planArtifact TYPE datetime DEFAULT time::now() READONLY;
160
190
 
161
191
  DEFINE INDEX IF NOT EXISTS planArtifactRunIdx ON TABLE planArtifact COLUMNS runId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lota-sdk/core",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -32,7 +32,7 @@
32
32
  "@chat-adapter/slack": "^4.23.0",
33
33
  "@chat-adapter/state-ioredis": "^4.23.0",
34
34
  "@logtape/logtape": "^2.0.5",
35
- "@lota-sdk/shared": "0.4.0",
35
+ "@lota-sdk/shared": "0.4.2",
36
36
  "@mendable/firecrawl-js": "^4.18.1",
37
37
  "@surrealdb/node": "^3.0.3",
38
38
  "ai": "^6.0.145",
@@ -29,6 +29,8 @@ import type { LotaRuntimeSocialChat } from './runtime/social-chat'
29
29
  import { createSocialChatRuntime } from './runtime/social-chat'
30
30
  import type { agentActivityService } from './services/agent-activity.service'
31
31
  import { agentActivityService as agentActivityServiceSingleton } from './services/agent-activity.service'
32
+ import type { artifactService } from './services/artifact.service'
33
+ import { artifactService as artifactServiceSingleton } from './services/artifact.service'
32
34
  import type { attachmentService } from './services/attachment.service'
33
35
  import { attachmentService as attachmentServiceSingleton } from './services/attachment.service'
34
36
  import type { autonomousJobService } from './services/autonomous-job.service'
@@ -46,8 +48,18 @@ import type { organizationMemberService } from './services/organization-member.s
46
48
  import { organizationMemberService as organizationMemberServiceSingleton } from './services/organization-member.service'
47
49
  import type { organizationService } from './services/organization.service'
48
50
  import { organizationService as organizationServiceSingleton } from './services/organization.service'
51
+ import type { planAgentHeartbeatService } from './services/plan-agent-heartbeat.service'
52
+ import { planAgentHeartbeatService as planAgentHeartbeatServiceSingleton } from './services/plan-agent-heartbeat.service'
49
53
  import type { planAgentQueryService } from './services/plan-agent-query.service'
50
54
  import { planAgentQueryService as planAgentQueryServiceSingleton } from './services/plan-agent-query.service'
55
+ import type { planCoordinationService } from './services/plan-coordination.service'
56
+ import { planCoordinationService as planCoordinationServiceSingleton } from './services/plan-coordination.service'
57
+ import type { planCycleService } from './services/plan-cycle.service'
58
+ import { planCycleService as planCycleServiceSingleton } from './services/plan-cycle.service'
59
+ import type { planSchedulerService } from './services/plan-scheduler.service'
60
+ import { planSchedulerService as planSchedulerServiceSingleton } from './services/plan-scheduler.service'
61
+ import type { planTemplateService } from './services/plan-template.service'
62
+ import { planTemplateService as planTemplateServiceSingleton } from './services/plan-template.service'
51
63
  import type { recentActivityTitleService } from './services/recent-activity-title.service'
52
64
  import { recentActivityTitleService as recentActivityTitleServiceSingleton } from './services/recent-activity-title.service'
53
65
  import type { recentActivityService } from './services/recent-activity.service'
@@ -117,6 +129,7 @@ export interface LotaRuntime {
117
129
  redis: RedisConnectionManager
118
130
  closeRedisConnection: () => Promise<void>
119
131
  agentActivityService: typeof agentActivityService
132
+ artifactService: typeof artifactService
120
133
  attachmentService: typeof attachmentService
121
134
  autonomousJobService: typeof autonomousJobService
122
135
  documentChunkService: typeof documentChunkService
@@ -130,7 +143,12 @@ export interface LotaRuntime {
130
143
  recentActivityTitleService: typeof recentActivityTitleService
131
144
  socialChatHistoryService: typeof socialChatHistoryServiceSingleton
132
145
  executionPlanService: typeof executionPlanService
146
+ planTemplateService: typeof planTemplateService
147
+ planCoordinationService: typeof planCoordinationService
148
+ planSchedulerService: typeof planSchedulerService
149
+ planAgentHeartbeatService: typeof planAgentHeartbeatService
133
150
  planAgentQueryService: typeof planAgentQueryService
151
+ planCycleService: typeof planCycleService
134
152
  threadMessageService: typeof threadMessageService
135
153
  threadService: typeof threadService
136
154
  threadTitleService: typeof threadTitleService
@@ -276,8 +294,12 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
276
294
  }
277
295
 
278
296
  const pluginContributions = Object.values(pluginRuntime).map((plugin) => plugin.contributions)
279
- const schemaFiles = [...getBuiltInSchemaFiles(), ...(runtimeConfig.extraSchemaFiles ?? [])]
280
297
  const hostContributionSchemaFiles = pluginContributions.flatMap((plugin) => plugin.schemaFiles)
298
+ const schemaFiles = [
299
+ ...getBuiltInSchemaFiles(),
300
+ ...(runtimeConfig.extraSchemaFiles ?? []),
301
+ ...hostContributionSchemaFiles,
302
+ ]
281
303
  const contributionEnvKeys = [...LOTA_RUNTIME_ENV_KEYS, ...pluginContributions.flatMap((plugin) => plugin.envKeys)]
282
304
  const connectPluginDatabases = createPluginDatabaseConnector(pluginRuntime)
283
305
  const workers = buildRuntimeWorkerRegistry(runtimeConfig.extraWorkers)
@@ -377,6 +399,7 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
377
399
  redis: redisManager,
378
400
  closeRedisConnection: async () => await redisManager.closeConnection(),
379
401
  agentActivityService: agentActivityServiceSingleton,
402
+ artifactService: artifactServiceSingleton,
380
403
  attachmentService: attachmentServiceSingleton,
381
404
  autonomousJobService: autonomousJobServiceSingleton,
382
405
  documentChunkService: documentChunkServiceSingleton,
@@ -390,7 +413,12 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
390
413
  recentActivityTitleService: recentActivityTitleServiceSingleton,
391
414
  socialChatHistoryService: socialChatHistoryServiceSingleton,
392
415
  executionPlanService: executionPlanServiceSingleton,
416
+ planTemplateService: planTemplateServiceSingleton,
417
+ planCoordinationService: planCoordinationServiceSingleton,
418
+ planSchedulerService: planSchedulerServiceSingleton,
419
+ planAgentHeartbeatService: planAgentHeartbeatServiceSingleton,
393
420
  planAgentQueryService: planAgentQueryServiceSingleton,
421
+ planCycleService: planCycleServiceSingleton,
394
422
  threadMessageService: threadMessageServiceSingleton,
395
423
  threadService: threadServiceSingleton,
396
424
  threadTitleService: threadTitleServiceSingleton,
package/src/db/tables.ts CHANGED
@@ -13,6 +13,7 @@ export const TABLES = {
13
13
  PLAN_NODE_RUN: 'planNodeRun',
14
14
  PLAN_NODE_ATTEMPT: 'planNodeAttempt',
15
15
  PLAN_ARTIFACT: 'planArtifact',
16
+ ARTIFACT: 'artifact',
16
17
  PLAN_APPROVAL: 'planApproval',
17
18
  PLAN_CHECKPOINT: 'planCheckpoint',
18
19
  PLAN_VALIDATION_ISSUE: 'planValidationIssue',
@@ -48,7 +48,7 @@ export async function enqueueAutonomousJobRun(params: {
48
48
  payload: AutonomousJobQueuePayload
49
49
  delayMs?: number
50
50
  jobId?: string
51
- }): Promise<{ bullmqJobId: string; queueJobId: string }> {
51
+ }): Promise<{ bullmqJobId: string; queueJobId?: string }> {
52
52
  const queuedJob = await autonomousJobQueue
53
53
  .getQueue()
54
54
  .add('run-autonomous-job', params.payload, {
@@ -56,17 +56,24 @@ export async function enqueueAutonomousJobRun(params: {
56
56
  ...(params.jobId ? { jobId: params.jobId } : {}),
57
57
  })
58
58
 
59
- const queueJobId = await queueJobService.recordEnqueued({
60
- queueName: AUTONOMOUS_JOB_QUEUE,
61
- id: queuedJob.id,
62
- name: queuedJob.name,
63
- data: queuedJob.data,
64
- opts: queuedJob.opts,
65
- attemptsMade: queuedJob.attemptsMade,
66
- timestamp: queuedJob.timestamp,
67
- })
59
+ const bullmqJobId = String(queuedJob.id)
60
+ let queueJobId: string | undefined
61
+
62
+ try {
63
+ queueJobId = await queueJobService.recordEnqueued({
64
+ queueName: AUTONOMOUS_JOB_QUEUE,
65
+ id: queuedJob.id,
66
+ name: queuedJob.name,
67
+ data: queuedJob.data,
68
+ opts: queuedJob.opts,
69
+ attemptsMade: queuedJob.attemptsMade,
70
+ timestamp: queuedJob.timestamp,
71
+ })
72
+ } catch (error) {
73
+ serverLogger.error`Failed to persist queued job metadata (queue=${AUTONOMOUS_JOB_QUEUE}, job=${queuedJob.id}): ${error}`
74
+ }
68
75
 
69
- return { bullmqJobId: String(queuedJob.id), queueJobId }
76
+ return { bullmqJobId, queueJobId }
70
77
  }
71
78
 
72
79
  export async function upsertAutonomousJobScheduler(params: {
@@ -83,15 +90,19 @@ export async function upsertAutonomousJobScheduler(params: {
83
90
  opts: DEFAULT_AUTONOMOUS_JOB_OPTIONS,
84
91
  })
85
92
 
86
- await queueJobService.recordEnqueued({
87
- queueName: AUTONOMOUS_JOB_QUEUE,
88
- id: queuedJob.id,
89
- name: queuedJob.name,
90
- data: queuedJob.data,
91
- opts: queuedJob.opts,
92
- attemptsMade: queuedJob.attemptsMade,
93
- timestamp: queuedJob.timestamp,
94
- })
93
+ try {
94
+ await queueJobService.recordEnqueued({
95
+ queueName: AUTONOMOUS_JOB_QUEUE,
96
+ id: queuedJob.id,
97
+ name: queuedJob.name,
98
+ data: queuedJob.data,
99
+ opts: queuedJob.opts,
100
+ attemptsMade: queuedJob.attemptsMade,
101
+ timestamp: queuedJob.timestamp,
102
+ })
103
+ } catch (error) {
104
+ serverLogger.error`Failed to persist queued job metadata (queue=${AUTONOMOUS_JOB_QUEUE}, job=${queuedJob.id}): ${error}`
105
+ }
95
106
  }
96
107
 
97
108
  export async function removeAutonomousJobScheduler(autonomousJobId: string): Promise<void> {
@@ -98,15 +98,19 @@ export function createDocumentProcessorQueueRuntime<TJob extends DocumentProcess
98
98
  return {
99
99
  enqueue: async (job) => {
100
100
  const queuedJob = await getQueue().add(jobName, toQueueData(job), { jobId: buildDocumentProcessorJobId(job) })
101
- await queueJobService.recordEnqueued({
102
- queueName,
103
- id: queuedJob.id,
104
- name: queuedJob.name,
105
- data: queuedJob.data,
106
- opts: queuedJob.opts,
107
- attemptsMade: queuedJob.attemptsMade,
108
- timestamp: queuedJob.timestamp,
109
- })
101
+ try {
102
+ await queueJobService.recordEnqueued({
103
+ queueName,
104
+ id: queuedJob.id,
105
+ name: queuedJob.name,
106
+ data: queuedJob.data,
107
+ opts: queuedJob.opts,
108
+ attemptsMade: queuedJob.attemptsMade,
109
+ timestamp: queuedJob.timestamp,
110
+ })
111
+ } catch (error) {
112
+ params.logger.error`Failed to persist queued job metadata (queue=${queueName}, job=${queuedJob.id}): ${error}`
113
+ }
110
114
  },
111
115
  startWorker: (options = {}) => {
112
116
  const { registerSignals = import.meta.main } = options
@@ -1,3 +1,4 @@
1
+ import { serverLogger } from '../config/logger'
1
2
  import { queueJobService } from '../services/queue-job.service'
2
3
  import { getWorkerPath, LONG_JOB_LOCK_DURATION_MS, LOW_JOB_RETENTION } from '../workers/worker-utils'
3
4
  import { createQueueFactory } from './queue-factory'
@@ -36,15 +37,19 @@ export async function scheduleRecurringConsolidation() {
36
37
  },
37
38
  )
38
39
 
39
- await queueJobService.recordEnqueued({
40
- queueName: 'memory-consolidation',
41
- id: queuedJob.id,
42
- name: queuedJob.name,
43
- data: queuedJob.data,
44
- opts: queuedJob.opts,
45
- attemptsMade: queuedJob.attemptsMade,
46
- timestamp: queuedJob.timestamp,
47
- })
40
+ try {
41
+ await queueJobService.recordEnqueued({
42
+ queueName: 'memory-consolidation',
43
+ id: queuedJob.id,
44
+ name: queuedJob.name,
45
+ data: queuedJob.data,
46
+ opts: queuedJob.opts,
47
+ attemptsMade: queuedJob.attemptsMade,
48
+ timestamp: queuedJob.timestamp,
49
+ })
50
+ } catch (error) {
51
+ serverLogger.error`Failed to persist queued job metadata (queue=memory-consolidation, job=${queuedJob.id}): ${error}`
52
+ }
48
53
  }
49
54
 
50
55
  export const startMemoryConsolidationWorker = memoryConsolidation.startWorker
@@ -85,15 +85,19 @@ export function createQueueFactory<TJob>(config: QueueFactoryConfig<TJob>): Queu
85
85
 
86
86
  const enqueue = async (job: TJob, options?: JobsOptions): Promise<void> => {
87
87
  const queuedJob = await getQueue().add(jobName, toData(job), options)
88
- await queueJobService.recordEnqueued({
89
- queueName: config.name,
90
- id: queuedJob.id,
91
- name: queuedJob.name,
92
- data: queuedJob.data,
93
- opts: queuedJob.opts,
94
- attemptsMade: queuedJob.attemptsMade,
95
- timestamp: queuedJob.timestamp,
96
- })
88
+ try {
89
+ await queueJobService.recordEnqueued({
90
+ queueName: config.name,
91
+ id: queuedJob.id,
92
+ name: queuedJob.name,
93
+ data: queuedJob.data,
94
+ opts: queuedJob.opts,
95
+ attemptsMade: queuedJob.attemptsMade,
96
+ timestamp: queuedJob.timestamp,
97
+ })
98
+ } catch (error) {
99
+ serverLogger.error`Failed to persist queued job metadata (queue=${config.name}, job=${queuedJob.id}): ${error}`
100
+ }
97
101
  }
98
102
 
99
103
  const startWorker = (options: { registerSignals?: boolean } = {}): WorkerHandle => {
@@ -74,7 +74,9 @@ function buildOwnershipDispatchArtifactPayload(artifacts: PlanArtifactSubmission
74
74
  name: artifact.name,
75
75
  kind: artifact.kind,
76
76
  ...(artifact.description ? { description: artifact.description } : {}),
77
+ ...(artifact.content !== undefined ? { content: artifact.content } : {}),
77
78
  ...(artifact.payload !== undefined ? { payload: artifact.payload } : {}),
79
+ ...(artifact.publishedArtifactId ? { publishedArtifactId: artifact.publishedArtifactId } : {}),
78
80
  }))
79
81
  }
80
82
 
@@ -1,3 +1,6 @@
1
+ import { toTimestamp } from '@lota-sdk/shared'
2
+ import type { ChatMessage } from '@lota-sdk/shared'
3
+
1
4
  import { agentDisplayNames, resolveAgentNameAlias } from '../config/agent-defaults'
2
5
  import type { ChatMessageLike, ReadableUploadMetadataLike } from './chat-types'
3
6
 
@@ -142,3 +145,54 @@ export function collectToolOutputErrors(params: {
142
145
 
143
146
  return errors
144
147
  }
148
+
149
+ export function collectCompletedConsultTeamMessages(params: { responseMessage: ChatMessageLike }): ChatMessage[] {
150
+ const messagesById = new Map<string, ChatMessage>()
151
+
152
+ for (const part of params.responseMessage.parts) {
153
+ if (typeof part !== 'object') continue
154
+ if (part.type !== 'tool-consultTeam') continue
155
+
156
+ const toolPart = part as Record<string, unknown>
157
+ if (toolPart.state !== 'output-available') continue
158
+
159
+ const output = asRecord(toolPart.output)
160
+ const responses = output?.responses
161
+ if (!Array.isArray(responses)) continue
162
+
163
+ for (const response of responses) {
164
+ const responseRecord = asRecord(response)
165
+ if (!responseRecord || responseRecord.status !== 'complete') continue
166
+
167
+ const messageRecord = asRecord(responseRecord.message)
168
+ if (!messageRecord || messageRecord.role !== 'assistant') continue
169
+
170
+ const id = readOptionalString(messageRecord.id)
171
+ if (!id) continue
172
+
173
+ const rawParts = Array.isArray(messageRecord.parts)
174
+ ? messageRecord.parts.map((item) => structuredClone(item) as Record<string, unknown>)
175
+ : []
176
+ if (rawParts.length === 0) continue
177
+
178
+ const responseAgentId = readOptionalString(responseRecord.agentId)
179
+ const responseAgentName = readOptionalString(responseRecord.agentName)
180
+ const metadata = {
181
+ ...asRecord(messageRecord.metadata),
182
+ ...(responseAgentId ? { agentId: responseAgentId } : {}),
183
+ ...(responseAgentName ? { agentName: responseAgentName } : {}),
184
+ }
185
+
186
+ messagesById.set(id, { id, role: 'assistant', parts: rawParts as ChatMessage['parts'], metadata })
187
+ }
188
+ }
189
+
190
+ return [...messagesById.values()].sort((left, right) => {
191
+ const leftCreatedAt = toTimestamp(left.metadata?.createdAt) ?? Number.MAX_SAFE_INTEGER
192
+ const rightCreatedAt = toTimestamp(right.metadata?.createdAt) ?? Number.MAX_SAFE_INTEGER
193
+ if (leftCreatedAt !== rightCreatedAt) {
194
+ return leftCreatedAt - rightCreatedAt
195
+ }
196
+ return left.id.localeCompare(right.id)
197
+ })
198
+ }